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