fix swfdec_as_context_startup() to work on non-SwfdecPlayer objects
[swfdec.git] / libswfdec / swfdec_as_context.c
blob617208ba5bee650a3245118a234f8426545d6e9f
1 /* Swfdec
2 * Copyright (C) 2007 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_initialize.h"
32 #include "swfdec_as_internal.h"
33 #include "swfdec_as_interpret.h"
34 #include "swfdec_as_native_function.h"
35 #include "swfdec_as_object.h"
36 #include "swfdec_as_stack.h"
37 #include "swfdec_as_strings.h"
38 #include "swfdec_as_types.h"
39 #include "swfdec_debug.h"
40 #include "swfdec_internal.h" /* for swfdec_player_preinit_global() */
41 #include "swfdec_script.h"
43 /*** GARBAGE COLLECTION DOCS ***/
45 #define SWFDEC_AS_GC_MARK (1 << 0) /* only valid during GC */
46 #define SWFDEC_AS_GC_ROOT (1 << 1) /* for objects: rooted, for strings: static */
48 /**
49 * SECTION:Internals
50 * @title: Internals and garbage collection
51 * @short_description: understanding internals such as garbage collection
52 * @see_also: #SwfdecAsContext
54 * This section deals with the internals of the Swfdec Actionscript engine. You
55 * should probably read this first when trying to write code with it. If you're
56 * just trying to use Swfdec for creating Flash content, you can probably skip
57 * this section.
59 * First, I'd like to note that the Swfdec script engine has to be modeled very
60 * closely after the existing Flash players. So if there are some behaviours
61 * that seem stupid at first sight, it might very well be that it was chosen for
62 * a very particular reason. Now on to the features.
64 * The Swfdec script engine tries to imitate Actionscript as good as possible.
65 * Actionscript is similar to Javascript, but not equal. Depending on the
66 * version of the script executed it might be more or less similar. An important
67 * example is that Flash in versions up to 6 did case-insensitive variable
68 * lookups.
70 * The script engine starts its life when it is initialized via
71 * swfdec_as_context_startup (). At that point, the basic objects are created.
72 * After this function has been called, you can start executing code. All code
73 * execution happens by creating a new #SwfdecAsFrame and then calling
74 * swfdec_as_context_run() to execute it. This function is the single entry
75 * point for code execution. Convenience functions exist that make executing
76 * code easy, most notably swfdec_as_object_run() and
77 * swfdec_as_object_call().
79 * It is also easily possible to extend the environment by adding new objects.
80 * In fact, without doing so, the environment is pretty bare as it just contains
81 * the basic Math, String, Number, Array, Date and Boolean objects. This is done
82 * by adding #SwfdecAsNative functions to the environment. The easy way
83 * to do this is via swfdec_as_object_add_function().
85 * The Swfdec script engine is dynamically typed and knows about different types
86 * of values. See #SwfdecAsValue for the different values. Memory management is
87 * done using a mark and sweep garbage collector. You can initiate a garbage
88 * collection cycle by calling swfdec_as_context_gc() or
89 * swfdec_as_context_maybe_gc(). You should do this regularly to avoid excessive
90 * memory use. The #SwfdecAsContext will then collect the objects and strings it
91 * is keeping track of. If you want to use an object or string in the script
92 * engine, you'll have to add it first via swfdec_as_object_add() or
93 * swfdec_as_context_get_string() and swfdec_as_context_give_string(),
94 * respectively.
96 * Garbage-collected strings are special in Swfdec in that they are unique. This
97 * means the same string exists exactly once. Because of this you can do
98 * equality comparisons using == instead of strcmp. It also means that if you
99 * forget to add a string to the context before using it, your app will most
100 * likely not work, since the string will not compare equal to any other string.
102 * When a garbage collection cycle happens, all reachable objects and strings
103 * are marked and all unreachable ones are freed. This is done by calling the
104 * context class's mark function which will mark all reachable objects. This is
105 * usually called the "root set". For any reachable object, the object's mark
106 * function is called so that the object in turn can mark all objects it can
107 * reach itself. Marking is done via functions described below.
110 /*** GTK-DOC ***/
113 * SECTION:SwfdecAsContext
114 * @title: SwfdecAsContext
115 * @short_description: the main script engine context
116 * @see_also: SwfdecPlayer
118 * A #SwfdecAsContext provides the main execution environment for Actionscript
119 * execution. It provides the objects typically available in ECMAScript and
120 * manages script execution, garbage collection etc. #SwfdecPlayer is a
121 * subclass of the context that implements Flash specific objects on top of it.
122 * However, it is possible to use the context for completely different functions
123 * where a sandboxed scripting environment is needed. An example is the Swfdec
124 * debugger.
125 * <note>The Actionscript engine is similar, but not equal to Javascript. It
126 * is not very different, but it is different.</note>
130 * SwfdecAsContext
132 * This is the main object ued to hold the state of a script engine. All members
133 * are private and should not be accessed.
135 * Subclassing this structure to get scripting support in your own appliation is
136 * encouraged.
140 * SwfdecAsContextState
141 * @SWFDEC_AS_CONTEXT_NEW: the context is not yet initialized,
142 * swfdec_as_context_startup() needs to be called.
143 * @SWFDEC_AS_CONTEXT_RUNNING: the context is running normally
144 * @SWFDEC_AS_CONTEXT_INTERRUPTED: the context has been interrupted by a
145 * debugger
146 * @SWFDEC_AS_CONTEXT_ABORTED: the context has aborted execution due to a
147 * fatal error
149 * The state of the context describes what operations are possible on the context.
150 * It will be in the state @SWFDEC_AS_CONTEXT_STATE_RUNNING almost all the time. If
151 * it is in the state @SWFDEC_AS_CONTEXT_STATE_ABORTED, it will not work anymore and
152 * every operation on it will instantly fail.
155 /*** RUNNING STATE ***/
158 * swfdec_as_context_abort:
159 * @context: a #SwfdecAsContext
160 * @reason: a string describing why execution was aborted
162 * Aborts script execution in @context. Call this functon if the script engine
163 * encountered a fatal error and cannot continue. A possible reason for this is
164 * an out-of-memory condition.
166 void
167 swfdec_as_context_abort (SwfdecAsContext *context, const char *reason)
169 g_return_if_fail (context);
171 SWFDEC_ERROR ("%s", reason);
172 context->state = SWFDEC_AS_CONTEXT_ABORTED;
175 /*** MEMORY MANAGEMENT ***/
178 * swfdec_as_context_use_mem:
179 * @context: a #SwfdecAsContext
180 * @bytes: number of bytes to use
182 * Registers @bytes additional bytes as in use by the @context. This function
183 * keeps track of the memory that script code consumes. If too many memory is
184 * in use, this function may decide to stop the script engine with an out of
185 * memory error.
187 * Returns: %TRUE if the memory could be allocated. %FALSE on OOM.
189 gboolean
190 swfdec_as_context_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 /* FIXME: Don't foget to abort on OOM */
201 return TRUE;
205 * swfdec_as_context_unuse_mem:
206 * @context: a #SwfdecAsContext
207 * @bytes: number of bytes to release
209 * Releases a number of bytes previously allocated using
210 * swfdec_as_context_use_mem(). See that function for details.
212 void
213 swfdec_as_context_unuse_mem (SwfdecAsContext *context, gsize bytes)
215 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
216 g_return_if_fail (bytes > 0);
217 g_return_if_fail (context->memory >= bytes);
219 context->memory -= bytes;
222 /*** GC ***/
224 static gboolean
225 swfdec_as_context_remove_strings (gpointer key, gpointer value, gpointer data)
227 SwfdecAsContext *context = data;
228 char *string;
230 string = (char *) value;
231 /* it doesn't matter that rooted strings aren't destroyed, they're constant */
232 if (string[0] & SWFDEC_AS_GC_ROOT) {
233 SWFDEC_LOG ("rooted: %s", (char *) key);
234 return FALSE;
235 } else if (string[0] & SWFDEC_AS_GC_MARK) {
236 SWFDEC_LOG ("marked: %s", (char *) key);
237 string[0] &= ~SWFDEC_AS_GC_MARK;
238 return FALSE;
239 } else {
240 gsize len;
241 SWFDEC_LOG ("deleted: %s", (char *) key);
242 len = (strlen ((char *) key) + 2);
243 swfdec_as_context_unuse_mem (context, len);
244 g_slice_free1 (len, value);
245 return TRUE;
249 static gboolean
250 swfdec_as_context_remove_objects (gpointer key, gpointer value, gpointer debugger)
252 SwfdecAsObject *object;
254 object = key;
255 /* we only check for mark here, not root, since this works on destroy, too */
256 if (object->flags & SWFDEC_AS_GC_MARK) {
257 object->flags &= ~SWFDEC_AS_GC_MARK;
258 SWFDEC_LOG ("%s: %s %p", (object->flags & SWFDEC_AS_GC_ROOT) ? "rooted" : "marked",
259 G_OBJECT_TYPE_NAME (object), object);
260 return FALSE;
261 } else {
262 SWFDEC_LOG ("deleted: %s %p", G_OBJECT_TYPE_NAME (object), object);
263 if (debugger) {
264 SwfdecAsDebuggerClass *klass = SWFDEC_AS_DEBUGGER_GET_CLASS (debugger);
265 if (klass->remove)
266 klass->remove (debugger, object->context, object);
268 swfdec_as_object_collect (object);
269 return TRUE;
273 static void
274 swfdec_as_context_collect (SwfdecAsContext *context)
276 SWFDEC_INFO (">> collecting garbage");
277 /* NB: This functions is called without GC from swfdec_as_context_dispose */
278 g_hash_table_foreach_remove (context->strings,
279 swfdec_as_context_remove_strings, context);
280 g_hash_table_foreach_remove (context->objects,
281 swfdec_as_context_remove_objects, context->debugger);
282 SWFDEC_INFO (">> done collecting garbage");
286 * swfdec_as_object_mark:
287 * @object: a #SwfdecAsObject
289 * Mark @object as being in use. Calling this function is only valid during
290 * the marking phase of garbage collection.
292 void
293 swfdec_as_object_mark (SwfdecAsObject *object)
295 SwfdecAsObjectClass *klass;
297 g_return_if_fail (SWFDEC_IS_AS_OBJECT (object));
299 if (object->flags & SWFDEC_AS_GC_MARK)
300 return;
301 object->flags |= SWFDEC_AS_GC_MARK;
302 klass = SWFDEC_AS_OBJECT_GET_CLASS (object);
303 g_assert (klass->mark);
304 klass->mark (object);
308 * swfdec_as_string_mark:
309 * @string: a garbage-collected string
311 * Mark @string as being in use. Calling this function is only valid during
312 * the marking phase of garbage collection.
314 void
315 swfdec_as_string_mark (const char *string)
317 char *str;
319 g_return_if_fail (string != NULL);
321 str = (char *) string - 1;
322 if (*str == 0)
323 *str = SWFDEC_AS_GC_MARK;
327 * swfdec_as_value_mark:
328 * @value: a #SwfdecAsValue
330 * Mark @value as being in use. This is just a convenience function that calls
331 * the right marking function depending on the value's type. Calling this
332 * function is only valid during the marking phase of garbage collection.
334 void
335 swfdec_as_value_mark (SwfdecAsValue *value)
337 g_return_if_fail (SWFDEC_IS_AS_VALUE (value));
339 if (SWFDEC_AS_VALUE_IS_OBJECT (value)) {
340 swfdec_as_object_mark (SWFDEC_AS_VALUE_GET_OBJECT (value));
341 } else if (SWFDEC_AS_VALUE_IS_STRING (value)) {
342 swfdec_as_string_mark (SWFDEC_AS_VALUE_GET_STRING (value));
346 static void
347 swfdec_as_context_mark_roots (gpointer key, gpointer value, gpointer data)
349 SwfdecAsObject *object = key;
351 if ((object->flags & (SWFDEC_AS_GC_MARK | SWFDEC_AS_GC_ROOT)) == SWFDEC_AS_GC_ROOT)
352 swfdec_as_object_mark (object);
355 static void
356 swfdec_as_context_do_mark (SwfdecAsContext *context)
358 swfdec_as_object_mark (context->global);
359 swfdec_as_object_mark (context->Function);
360 if (context->Function_prototype)
361 swfdec_as_object_mark (context->Function_prototype);
362 swfdec_as_object_mark (context->Object);
363 swfdec_as_object_mark (context->Object_prototype);
364 g_hash_table_foreach (context->objects, swfdec_as_context_mark_roots, NULL);
368 * swfdec_as_context_gc:
369 * @context: a #SwfdecAsContext
371 * Calls the Swfdec Gargbage collector and reclaims any unused memory. You
372 * should call this function or swfdec_as_context_maybe_gc() regularly.
373 * <warning>Calling the GC during execution of code or initialization is not
374 * allowed.</warning>
376 void
377 swfdec_as_context_gc (SwfdecAsContext *context)
379 SwfdecAsContextClass *klass;
381 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
382 g_return_if_fail (context->frame == NULL);
383 g_return_if_fail (context->state == SWFDEC_AS_CONTEXT_RUNNING);
385 if (context->state == SWFDEC_AS_CONTEXT_ABORTED)
386 return;
387 SWFDEC_INFO ("invoking the garbage collector");
388 klass = SWFDEC_AS_CONTEXT_GET_CLASS (context);
389 g_assert (klass->mark);
390 klass->mark (context);
391 swfdec_as_context_collect (context);
392 context->memory_since_gc = 0;
395 static gboolean
396 swfdec_as_context_needs_gc (SwfdecAsContext *context)
398 return context->memory_since_gc >= context->memory_until_gc;
402 * swfdec_as_context_maybe_gc:
403 * @context: a #SwfdecAsContext
405 * Calls the garbage collector if necessary. It's a good idea to call this
406 * function regularly instead of swfdec_as_context_gc() as it only does collect
407 * garage as needed. For example, #SwfdecPlayer calls this function after every
408 * frame advancement.
410 void
411 swfdec_as_context_maybe_gc (SwfdecAsContext *context)
413 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
414 g_return_if_fail (context->state == SWFDEC_AS_CONTEXT_RUNNING);
415 g_return_if_fail (context->frame == NULL);
417 if (swfdec_as_context_needs_gc (context))
418 swfdec_as_context_gc (context);
421 /*** SWFDEC_AS_CONTEXT ***/
423 enum {
424 TRACE,
425 LAST_SIGNAL
428 enum {
429 PROP_0,
430 PROP_DEBUGGER,
431 PROP_UNTIL_GC
434 G_DEFINE_TYPE (SwfdecAsContext, swfdec_as_context, G_TYPE_OBJECT)
435 static guint signals[LAST_SIGNAL] = { 0, };
437 static void
438 swfdec_as_context_get_property (GObject *object, guint param_id, GValue *value,
439 GParamSpec * pspec)
441 SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
444 switch (param_id) {
445 case PROP_DEBUGGER:
446 g_value_set_object (value, context->debugger);
447 break;
448 case PROP_UNTIL_GC:
449 g_value_set_ulong (value, (gulong) context->memory_until_gc);
450 break;
451 default:
452 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
453 break;
457 static void
458 swfdec_as_context_set_property (GObject *object, guint param_id, const GValue *value,
459 GParamSpec * pspec)
461 SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
464 switch (param_id) {
465 case PROP_DEBUGGER:
466 context->debugger = SWFDEC_AS_DEBUGGER (g_value_dup_object (value));
467 break;
468 case PROP_UNTIL_GC:
469 context->memory_until_gc = g_value_get_ulong (value);
470 break;
471 default:
472 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
473 break;
477 static void
478 swfdec_as_context_dispose (GObject *object)
480 SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
482 while (context->stack)
483 swfdec_as_stack_pop_segment (context);
484 swfdec_as_context_collect (context);
485 if (context->memory != 0) {
486 g_critical ("%zu bytes of memory left over\n", context->memory);
488 g_assert (g_hash_table_size (context->objects) == 0);
489 g_hash_table_destroy (context->objects);
490 g_hash_table_destroy (context->strings);
491 g_rand_free (context->rand);
492 if (context->debugger) {
493 g_object_unref (context->debugger);
494 context->debugger = NULL;
497 G_OBJECT_CLASS (swfdec_as_context_parent_class)->dispose (object);
500 static void
501 swfdec_as_context_class_init (SwfdecAsContextClass *klass)
503 GObjectClass *object_class = G_OBJECT_CLASS (klass);
505 object_class->dispose = swfdec_as_context_dispose;
506 object_class->get_property = swfdec_as_context_get_property;
507 object_class->set_property = swfdec_as_context_set_property;
509 g_object_class_install_property (object_class, PROP_DEBUGGER,
510 g_param_spec_object ("debugger", "debugger", "debugger used in this player",
511 SWFDEC_TYPE_AS_DEBUGGER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
512 g_object_class_install_property (object_class, PROP_UNTIL_GC,
513 g_param_spec_ulong ("memory-until-gc", "memory until gc",
514 "amount of bytes that need to be allocated before garbage collection triggers",
515 0, G_MAXULONG, 8 * 1024 * 1024, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
518 * SwfdecAsContext::trace:
519 * @context: the #SwfdecAsContext affected
520 * @text: the debugging string
522 * Emits a debugging string while running. The effect of calling any swfdec
523 * functions on the emitting @context is undefined.
525 signals[TRACE] = g_signal_new ("trace", G_TYPE_FROM_CLASS (klass),
526 G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING,
527 G_TYPE_NONE, 1, G_TYPE_STRING);
529 klass->mark = swfdec_as_context_do_mark;
532 static void
533 swfdec_as_context_init (SwfdecAsContext *context)
535 const char *s;
537 context->strings = g_hash_table_new (g_str_hash, g_str_equal);
538 context->objects = g_hash_table_new (g_direct_hash, g_direct_equal);
540 for (s = swfdec_as_strings; *s == '\2'; s += strlen (s) + 1) {
541 g_hash_table_insert (context->strings, (char *) s + 1, (char *) s);
543 g_assert (*s == 0);
544 context->global = swfdec_as_object_new_empty (context);
545 context->rand = g_rand_new ();
546 g_get_current_time (&context->start_time);
549 /*** STRINGS ***/
551 static const char *
552 swfdec_as_context_create_string (SwfdecAsContext *context, const char *string, gsize len)
554 char *new;
556 if (!swfdec_as_context_use_mem (context, sizeof (char) * (2 + len)))
557 return SWFDEC_AS_STR_EMPTY;
559 new = g_slice_alloc (2 + len);
560 memcpy (&new[1], string, len);
561 new[len + 1] = 0;
562 new[0] = 0; /* GC flags */
563 g_hash_table_insert (context->strings, new + 1, new);
565 return new + 1;
569 * swfdec_as_context_get_string:
570 * @context: a #SwfdecAsContext
571 * @string: a sting that is not garbage-collected
573 * Gets the garbage-collected version of @string. You need to call this function
574 * for every not garbage-collected string that you want to use in Swfdecs script
575 * interpreter.
577 * Returns: the garbage-collected version of @string
579 const char *
580 swfdec_as_context_get_string (SwfdecAsContext *context, const char *string)
582 const char *ret;
583 gsize len;
585 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context), NULL);
586 g_return_val_if_fail (string != NULL, NULL);
588 if (g_hash_table_lookup_extended (context->strings, string, (gpointer) &ret, NULL))
589 return ret;
591 len = strlen (string);
592 return swfdec_as_context_create_string (context, string, len);
596 * swfdec_as_context_give_string:
597 * @context: a #SwfdecAsContext
598 * @string: string to make refcounted
600 * Takes ownership of @string and returns a refcounted version of the same
601 * string. This function is the same as swfdec_as_context_get_string(), but
602 * takes ownership of @string.
604 * Returns: A refcounted string
606 const char *
607 swfdec_as_context_give_string (SwfdecAsContext *context, char *string)
609 const char *ret;
611 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context), NULL);
612 g_return_val_if_fail (string != NULL, NULL);
614 ret = swfdec_as_context_get_string (context, string);
615 g_free (string);
616 return ret;
620 * swfdec_as_context_is_constructing:
621 * @context: a #SwfdecAsConstruct
623 * Determines if the contexxt is currently constructing. This information is
624 * used by various constructors to do different things when they are
625 * constructing and when they are not. The Boolean, Number and String functions
626 * for example setup the newly constructed objects when constructing but only
627 * cast the provided argument when being called.
629 * Returns: %TRUE if the currently executing frame is a constructor
631 gboolean
632 swfdec_as_context_is_constructing (SwfdecAsContext *context)
634 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context), FALSE);
636 return context->frame && context->frame->construct;
640 * swfdec_as_context_get_frame:
641 * @context: a #SwfdecAsContext
643 * This is a debugging function. It gets the topmost stack frame that is
644 * currently executing. If no function is executing, %NULL is returned. You can
645 * easily get a backtrace with code like this:
646 * |[for (frame = swfdec_as_context_get_frame (context); frame != NULL;
647 * frame = swfdec_as_frame_get_next (frame)) {
648 * char *s = swfdec_as_object_get_debug (SWFDEC_AS_OBJECT (frame));
649 * g_print ("%s\n", s);
650 * g_free (s);
651 * }]|
653 * Returns: the currently executing frame or %NULL if none
655 SwfdecAsFrame *
656 swfdec_as_context_get_frame (SwfdecAsContext *context)
658 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context), NULL);
660 return context->frame;
664 * swfdec_as_context_get_time:
665 * @context: a #SwfdecAsContext
666 * @tv: a #GTimeVal to be set to the context's time
668 * This function queries the time to be used inside this context. By default,
669 * this is the same as g_get_current_time(), but it may be overwriten to allow
670 * things such as slower or faster playback.
672 void
673 swfdec_as_context_get_time (SwfdecAsContext *context, GTimeVal *tv)
675 SwfdecAsContextClass *klass;
677 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
678 g_return_if_fail (tv != NULL);
680 klass = SWFDEC_AS_CONTEXT_GET_CLASS (context);
681 if (klass->get_time)
682 klass->get_time (context, tv);
683 else
684 g_get_current_time (tv);
688 * swfdec_as_context_run:
689 * @context: a #SwfdecAsContext
691 * Continues running the script engine. Executing code in this engine works
692 * in 2 steps: First, you push the frame to be executed onto the stack, then
693 * you call this function to execute it. So this function is the single entry
694 * point to script execution. This might be helpful when debugging your
695 * application.
696 * <note>A lot of convenience functions like swfdec_as_object_run() call this
697 * function automatically.</note>
699 void
700 swfdec_as_context_run (SwfdecAsContext *context)
702 SwfdecAsFrame *frame, *last_frame;
703 SwfdecScript *script;
704 const SwfdecActionSpec *spec;
705 SwfdecActionExec exec;
706 guint8 *startpc, *pc, *endpc, *nextpc;
707 #ifndef G_DISABLE_ASSERT
708 SwfdecAsValue *check;
709 #endif
710 guint action, len;
711 guint8 *data;
712 int version;
713 guint original_version;
714 void (* step) (SwfdecAsDebugger *debugger, SwfdecAsContext *context);
715 gboolean check_block; /* some opcodes avoid a scope check */
717 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
718 if (context->frame == NULL || context->state == SWFDEC_AS_CONTEXT_ABORTED)
719 return;
721 if (context->debugger) {
722 SwfdecAsDebuggerClass *klass = SWFDEC_AS_DEBUGGER_GET_CLASS (context->debugger);
723 step = klass->step;
724 } else {
725 step = NULL;
728 last_frame = context->last_frame;
729 context->last_frame = context->frame->next;
730 original_version = context->version;
731 start:
732 /* setup data */
733 frame = context->frame;
734 if (frame == context->last_frame)
735 goto out;
736 if (context->call_depth > 256) {
737 /* we've exceeded our maximum call depth, throw an error and abort */
738 swfdec_as_context_abort (context, "Stack overflow");
739 return;
741 if (SWFDEC_IS_AS_NATIVE_FUNCTION (frame->function)) {
742 SwfdecAsNativeFunction *native = SWFDEC_AS_NATIVE_FUNCTION (frame->function);
743 SwfdecAsValue rval = { 0, };
744 if (frame->argc >= native->min_args &&
745 (native->type == 0 ||
746 g_type_is_a (G_OBJECT_TYPE (frame->thisp), native->type))) {
747 SwfdecAsValue *argv;
748 /* accumulate argv */
749 if (frame->argc == 0 || frame->argv != NULL) {
750 /* FIXME FIXME FIXME: no casting here please! */
751 argv = (SwfdecAsValue *) frame->argv;
752 } else {
753 SwfdecAsStack *stack;
754 SwfdecAsValue *cur;
755 guint i, n;
756 if (frame->argc > 128) {
757 SWFDEC_FIXME ("allow calling native functions with more than 128 args");
758 n = 128;
759 } else {
760 n = frame->argc;
762 argv = g_new (SwfdecAsValue, n);
763 stack = context->stack;
764 cur = context->cur;
765 for (i = 0; i < n; i++) {
766 if (cur <= &stack->elements[0]) {
767 stack = stack->next;
768 cur = &stack->elements[stack->used_elements];
770 cur--;
771 argv[i] = *cur;
774 native->native (context, frame->thisp, frame->argc,
775 argv, &rval);
776 if (argv != frame->argv)
777 g_free (argv);
779 swfdec_as_frame_return (frame, &rval);
780 goto start;
782 g_assert (frame->script);
783 g_assert (frame->target);
784 script = frame->script;
785 version = SWFDEC_AS_EXTRACT_SCRIPT_VERSION (script->version);
786 context->version = script->version;
787 startpc = script->buffer->data;
788 endpc = startpc + script->buffer->length;
789 pc = frame->pc;
790 check_block = TRUE;
792 while (context->state < SWFDEC_AS_CONTEXT_ABORTED) {
793 if (check_block && (pc < frame->block_start || pc >= frame->block_end)) {
794 SWFDEC_LOG ("code exited block");
795 swfdec_as_frame_check_block (frame);
796 pc = frame->pc;
797 if (frame != context->frame)
798 goto start;
800 if (pc < startpc || pc >= endpc) {
801 if (pc == endpc) {
802 swfdec_as_frame_return (frame, NULL);
803 goto start;
805 SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc, startpc, endpc);
806 goto error;
809 /* decode next action */
810 action = *pc;
811 if (action == 0) {
812 swfdec_as_frame_return (frame, NULL);
813 goto start;
815 /* invoke debugger if there is one */
816 if (step) {
817 frame->pc = pc;
818 (* step) (context->debugger, context);
819 if (frame != context->frame ||
820 frame->pc != pc) {
821 goto start;
824 /* prepare action */
825 spec = swfdec_as_actions + action;
826 if (action & 0x80) {
827 if (pc + 2 >= endpc) {
828 SWFDEC_ERROR ("action %u length value out of range", action);
829 goto error;
831 data = pc + 3;
832 len = pc[1] | pc[2] << 8;
833 if (data + len > endpc) {
834 SWFDEC_ERROR ("action %u length %u out of range", action, len);
835 goto error;
837 nextpc = pc + 3 + len;
838 } else {
839 data = NULL;
840 len = 0;
841 nextpc = pc + 1;
843 /* check action is valid */
844 exec = spec->exec[version];
845 if (!exec) {
846 guint real_version;
847 for (real_version = version + 1; !exec &&
848 real_version <= SWFDEC_AS_MAX_SCRIPT_VERSION - SWFDEC_AS_MIN_SCRIPT_VERSION;
849 real_version++) {
850 exec = spec->exec[real_version];
852 if (!exec) {
853 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, skipping it", action,
854 action, spec->name ? spec->name : "Unknown", script->version);
855 frame->pc = pc = nextpc;
856 check_block = TRUE;
857 continue;
859 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, using version %u instead",
860 action, action, spec->name ? spec->name : "Unknown", script->version,
861 script->version + real_version - version);
863 if (spec->remove > 0) {
864 if (spec->add > spec->remove)
865 swfdec_as_stack_ensure_free (context, spec->add - spec->remove);
866 swfdec_as_stack_ensure_size (context, spec->remove);
867 } else {
868 if (spec->add > 0)
869 swfdec_as_stack_ensure_free (context, spec->add);
871 if (context->state > SWFDEC_AS_CONTEXT_RUNNING) {
872 SWFDEC_WARNING ("context not running anymore, aborting");
873 goto error;
875 #ifndef G_DISABLE_ASSERT
876 check = (spec->add >= 0 && spec->remove >= 0) ? context->cur + spec->add - spec->remove : NULL;
877 #endif
878 /* execute action */
879 exec (context, action, data, len);
880 /* adapt the pc if the action did not, otherwise, leave it alone */
881 /* FIXME: do this via flag? */
882 if (frame->pc == pc) {
883 frame->pc = pc = nextpc;
884 check_block = TRUE;
885 } else {
886 pc = frame->pc;
887 check_block = FALSE;
889 if (frame == context->frame) {
890 #ifndef G_DISABLE_ASSERT
891 if (check != NULL && check != context->cur) {
892 g_error ("action %s was supposed to change the stack by %d (+%d -%d), but it changed by %td",
893 spec->name, spec->add - spec->remove, spec->add, spec->remove,
894 context->cur - check + spec->add - spec->remove);
896 #endif
897 } else {
898 /* someone called/returned from a function, reread variables */
899 goto start;
903 error:
904 while (context->frame != context->last_frame)
905 swfdec_as_frame_return (context->frame, NULL);
906 out:
907 context->last_frame = last_frame;
908 context->version = original_version;
909 return;
912 /*** EVAL ***/
914 static char *
915 swfdec_as_slash_to_dot (const char *slash_str)
917 const char *cur = slash_str;
918 GString *str = g_string_new ("");
920 if (*cur == '/') {
921 g_string_append (str, "_root");
922 } else {
923 goto start;
925 while (cur && *cur == '/') {
926 cur++;
927 start:
928 if (str->len > 0)
929 g_string_append_c (str, '.');
930 if (cur[0] == '.' && cur[1] == '.') {
931 g_string_append (str, "_parent");
932 cur += 2;
933 } else {
934 char *slash = strchr (cur, '/');
935 if (slash) {
936 g_string_append_len (str, cur, slash - cur);
937 cur = slash;
938 } else {
939 g_string_append (str, cur);
940 cur = NULL;
943 /* cur should now point to the slash */
945 if (cur) {
946 if (*cur != '\0')
947 goto fail;
949 SWFDEC_DEBUG ("parsed slash-notated string \"%s\" into dot notation \"%s\"",
950 slash_str, str->str);
951 return g_string_free (str, FALSE);
953 fail:
954 SWFDEC_WARNING ("failed to parse slash-notated string \"%s\" into dot notation", slash_str);
955 g_string_free (str, TRUE);
956 return NULL;
959 static void
960 swfdec_as_context_eval_get_property (SwfdecAsContext *cx,
961 SwfdecAsObject *obj, const char *name, SwfdecAsValue *ret)
963 if (obj) {
964 swfdec_as_object_get_variable (obj, name, ret);
965 } else {
966 if (cx->frame) {
967 swfdec_as_frame_get_variable (cx->frame, name, ret);
968 } else {
969 SWFDEC_WARNING ("eval called without a frame");
970 swfdec_as_object_get_variable (cx->global, name, ret);
975 static void
976 swfdec_as_context_eval_set_property (SwfdecAsContext *cx,
977 SwfdecAsObject *obj, const char *name, const SwfdecAsValue *ret)
979 if (obj == NULL) {
980 if (cx->frame == NULL) {
981 SWFDEC_ERROR ("no frame in eval_set?");
982 return;
984 swfdec_as_frame_set_variable (cx->frame, name, ret);
985 } else {
986 swfdec_as_object_set_variable (obj, name, ret);
990 static void
991 swfdec_as_context_eval_internal (SwfdecAsContext *cx, SwfdecAsObject *obj, const char *str,
992 SwfdecAsValue *val, gboolean set)
994 SwfdecAsValue cur;
995 char **varlist;
996 guint i;
998 SWFDEC_LOG ("eval called with \"%s\" on %p", str, obj);
999 if (strchr (str, '/')) {
1000 char *work = swfdec_as_slash_to_dot (str);
1001 if (!work) {
1002 SWFDEC_AS_VALUE_SET_UNDEFINED (val);
1003 return;
1005 varlist = g_strsplit (work, ".", -1);
1006 g_free (work);
1007 } else {
1008 varlist = g_strsplit (str, ".", -1);
1010 for (i = 0; varlist[i] != NULL; i++) {
1011 const char *dot = swfdec_as_context_get_string (cx, varlist[i]);
1012 if (varlist[i+1] != NULL) {
1013 swfdec_as_context_eval_get_property (cx, obj, dot, &cur);
1014 if (!SWFDEC_AS_VALUE_IS_OBJECT (&cur)) {
1015 SWFDEC_AS_VALUE_SET_UNDEFINED (&cur);
1016 break;
1018 obj = SWFDEC_AS_VALUE_GET_OBJECT (&cur);
1019 } else {
1020 if (set) {
1021 swfdec_as_context_eval_set_property (cx, obj, dot, val);
1022 } else {
1023 swfdec_as_context_eval_get_property (cx, obj, dot, &cur);
1025 goto finish;
1028 if (obj == NULL) {
1029 if (cx->frame)
1030 obj = cx->frame->target;
1031 else
1032 obj = cx->global;
1034 g_assert (obj != NULL);
1035 SWFDEC_AS_VALUE_SET_OBJECT (&cur, obj);
1037 finish:
1038 g_strfreev (varlist);
1039 *val = cur;
1043 * swfdec_as_context_eval:
1044 * @context: a #SwfdecAsContext
1045 * @obj: #SwfdecAsObject to use as source for evaluating or NULL for the
1046 * current frame's scope
1047 * @str: The string to evaluate
1048 * @val: location for the return value
1050 * This function works like the Actionscript eval function used on @obj.
1051 * It handles both slash-style and dot-style notation. If an error occured
1052 * during evaluation, the return value will be the undefined value.
1054 void
1055 swfdec_as_context_eval (SwfdecAsContext *context, SwfdecAsObject *obj, const char *str,
1056 SwfdecAsValue *val)
1058 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
1059 g_return_if_fail (obj == NULL || SWFDEC_IS_AS_OBJECT (obj));
1060 g_return_if_fail (str != NULL);
1061 g_return_if_fail (val != NULL);
1063 swfdec_as_context_eval_internal (context, obj, str, val, FALSE);
1067 * swfdec_as_context_eval_set:
1068 * @context: a #SwfdecAsContext
1069 * @obj: #SwfdecAsObject to use as source for evaluating or NULL for the
1070 * default object.
1071 * @str: The string to evaluate
1072 * @val: the value to set the variable to
1074 * Sets the variable referenced by @str to @val. If @str does not reference
1075 * a valid property, nothing happens.
1077 void
1078 swfdec_as_context_eval_set (SwfdecAsContext *context, SwfdecAsObject *obj, const char *str,
1079 const SwfdecAsValue *val)
1081 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
1082 g_return_if_fail (obj == NULL || SWFDEC_IS_AS_OBJECT (obj));
1083 g_return_if_fail (str != NULL);
1084 g_return_if_fail (val != NULL);
1086 swfdec_as_context_eval_internal (context, obj, str, (SwfdecAsValue *) val, TRUE);
1089 /*** AS CODE ***/
1091 static void
1092 swfdec_as_context_ASSetPropFlags_set_one_flag (SwfdecAsObject *object,
1093 const char *s, guint *flags)
1095 swfdec_as_object_unset_variable_flags (object, s, flags[1]);
1096 swfdec_as_object_set_variable_flags (object, s, flags[0]);
1099 static gboolean
1100 swfdec_as_context_ASSetPropFlags_foreach (SwfdecAsObject *object,
1101 const char *s, SwfdecAsValue *val, guint cur_flags, gpointer data)
1103 guint *flags = data;
1105 /* shortcut if the flags already match */
1106 if (cur_flags == ((cur_flags &~ flags[1]) | flags[0]))
1107 return TRUE;
1109 swfdec_as_context_ASSetPropFlags_set_one_flag (object, s, flags);
1110 return TRUE;
1113 static void
1114 swfdec_as_context_ASSetPropFlags (SwfdecAsContext *cx, SwfdecAsObject *object,
1115 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1117 guint flags[2]; /* flags and mask - array so we can pass it as data pointer */
1118 SwfdecAsObject *obj;
1120 if (!SWFDEC_AS_VALUE_IS_OBJECT (&argv[0]))
1121 return;
1122 obj = SWFDEC_AS_VALUE_GET_OBJECT (&argv[0]);
1123 flags[0] = swfdec_as_value_to_integer (cx, &argv[2]);
1124 flags[1] = (argc > 3) ? swfdec_as_value_to_integer (cx, &argv[3]) : 0;
1126 if (flags[0] == 0 && flags[1] == 0) {
1127 // we should add autosizing length attribute here
1128 SWFDEC_FIXME ("ASSetPropFlags to set special length attribute not implemented");
1129 return;
1132 if (SWFDEC_AS_VALUE_IS_NULL (&argv[1])) {
1133 swfdec_as_object_foreach (obj, swfdec_as_context_ASSetPropFlags_foreach, flags);
1134 } else {
1135 char **split =
1136 g_strsplit (swfdec_as_value_to_string (cx, &argv[1]), ",", -1);
1137 guint i;
1138 for (i = 0; split[i]; i++) {
1139 swfdec_as_context_ASSetPropFlags_set_one_flag (obj,
1140 swfdec_as_context_get_string (cx, split[i]), flags);
1142 g_strfreev (split);
1146 SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite)
1147 void
1148 swfdec_as_context_isFinite (SwfdecAsContext *cx, SwfdecAsObject *object,
1149 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1151 double d;
1153 if (argc < 1)
1154 return;
1156 d = swfdec_as_value_to_number (cx, &argv[0]);
1157 SWFDEC_AS_VALUE_SET_BOOLEAN (retval, isfinite (d) ? TRUE : FALSE);
1160 SWFDEC_AS_NATIVE (200, 18, swfdec_as_context_isNaN)
1161 void
1162 swfdec_as_context_isNaN (SwfdecAsContext *cx, SwfdecAsObject *object,
1163 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1165 double d;
1167 if (argc < 1)
1168 return;
1170 d = swfdec_as_value_to_number (cx, &argv[0]);
1171 SWFDEC_AS_VALUE_SET_BOOLEAN (retval, isnan (d) ? TRUE : FALSE);
1174 SWFDEC_AS_NATIVE (100, 2, swfdec_as_context_parseInt)
1175 void
1176 swfdec_as_context_parseInt (SwfdecAsContext *cx, SwfdecAsObject *object,
1177 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1179 const char *s;
1180 char *tail;
1181 int radix;
1182 long int i;
1184 if (argc < 1)
1185 return;
1187 s = swfdec_as_value_to_string (cx, &argv[0]);
1189 if (argc >= 2) {
1190 radix = swfdec_as_value_to_integer (cx, &argv[1]);
1192 if (radix < 2 || radix > 36) {
1193 SWFDEC_AS_VALUE_SET_NUMBER (retval, NAN);
1194 return;
1197 // special case, strtol parses things that we shouldn't parse
1198 if (radix == 16) {
1199 const char *end = s + strspn (s, " \t\r\n");
1200 if (end != s && end[0] == '0' && end[1] == 'x') {
1201 SWFDEC_AS_VALUE_SET_NUMBER (retval, 0);
1202 return;
1205 } else {
1206 radix = 0;
1209 // special case
1210 if ((s[0] == '-' || s[0] == '+') && s[1] == '0' && s[2] == 'x') {
1211 SWFDEC_AS_VALUE_SET_NUMBER (retval, NAN);
1212 return;
1215 if (s[0] == '0' && s[1] == 'x') {
1216 s = s + 2;
1217 i = strtol (s, &tail, (radix != 0 ? radix : 16));
1218 } else {
1219 i = strtol (s, &tail, (radix != 0 ? radix : 10));
1222 if (tail == s) {
1223 SWFDEC_AS_VALUE_SET_NUMBER (retval, NAN);
1224 return;
1227 SWFDEC_AS_VALUE_SET_INT (retval, i);
1230 SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat)
1231 void
1232 swfdec_as_context_parseFloat (SwfdecAsContext *cx, SwfdecAsObject *object,
1233 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1235 char *s, *p, *tail;
1236 double d;
1238 if (argc < 1)
1239 return;
1241 // we need to remove everything after x or I, since strtod parses hexadecimal
1242 // numbers and Infinity
1243 s = g_strdup (swfdec_as_value_to_string (cx, &argv[0]));
1244 if ((p = strpbrk (s, "xI")) != NULL) {
1245 *p = '\0';
1248 d = strtod (s, &tail);
1250 if (tail == s) {
1251 SWFDEC_AS_VALUE_SET_NUMBER (retval, NAN);
1252 } else {
1253 SWFDEC_AS_VALUE_SET_NUMBER (retval, d);
1256 g_free (s);
1258 static void
1259 swfdec_as_context_init_global (SwfdecAsContext *context, guint version)
1261 SwfdecAsValue val;
1263 swfdec_as_object_add_function (context->global, SWFDEC_AS_STR_ASSetPropFlags, 0,
1264 swfdec_as_context_ASSetPropFlags, 3);
1265 SWFDEC_AS_VALUE_SET_NUMBER (&val, NAN);
1266 swfdec_as_object_set_variable (context->global, SWFDEC_AS_STR_NaN, &val);
1267 SWFDEC_AS_VALUE_SET_NUMBER (&val, HUGE_VAL);
1268 swfdec_as_object_set_variable (context->global, SWFDEC_AS_STR_Infinity, &val);
1271 void
1272 swfdec_as_context_run_init_script (SwfdecAsContext *context, const guint8 *data,
1273 gsize length, guint version)
1275 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
1276 g_return_if_fail (data != NULL);
1277 g_return_if_fail (length > 0);
1279 if (version > 4) {
1280 SwfdecBits bits;
1281 SwfdecScript *script;
1282 swfdec_bits_init_data (&bits, data, length);
1283 script = swfdec_script_new_from_bits (&bits, "init", version);
1284 if (script == NULL) {
1285 g_warning ("script passed to swfdec_as_context_run_init_script is invalid");
1286 return;
1288 swfdec_as_object_run (context->global, script);
1289 swfdec_script_unref (script);
1290 } else {
1291 SWFDEC_LOG ("not running init script, since version is <= 4");
1296 * swfdec_as_context_startup:
1297 * @context: a #SwfdecAsContext
1298 * @version: Flash version to use
1300 * Starts up the context. This function must be called before any Actionscript
1301 * is called on @context. The version is responsible for deciding which native
1302 * functions and properties are available in the context.
1304 void
1305 swfdec_as_context_startup (SwfdecAsContext *context, guint version)
1307 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context));
1308 g_return_if_fail (context->state == SWFDEC_AS_CONTEXT_NEW);
1310 if (!swfdec_as_stack_push_segment (context))
1311 return;
1312 context->version = version;
1313 /* init the two internal functions */
1314 /* FIXME: remove them for normal contexts? */
1315 swfdec_player_preinit_global (context, version);
1316 /* get the necessary objects up to define objects and functions sanely */
1317 swfdec_as_function_init_context (context, version);
1318 swfdec_as_object_init_context (context, version);
1319 /* define the global object and other important ones */
1320 swfdec_as_context_init_global (context, version);
1322 /* run init script */
1323 swfdec_as_context_run_init_script (context, swfdec_as_initialize, sizeof (swfdec_as_initialize), 8);
1325 if (context->state == SWFDEC_AS_CONTEXT_NEW)
1326 context->state = SWFDEC_AS_CONTEXT_RUNNING;