2
* Copyright (C) 2007-2008 Benjamin Otte <otte@gnome.org>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2.1 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with this library; if not, write to the Free Software
16
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
* Boston, MA 02110-1301 USA
27
#include "swfdec_as_context.h"
28
#include "swfdec_as_array.h"
29
#include "swfdec_as_frame_internal.h"
30
#include "swfdec_as_function.h"
31
#include "swfdec_as_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 ***/
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
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
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(),
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.
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
121
* <note>The Actionscript engine is similar, but not equal to Javascript. It
122
* is not very different, but it is different.</note>
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
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
142
* @SWFDEC_AS_CONTEXT_ABORTED: the context has aborted execution due to a
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.
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.
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)
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);
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
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.
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);
252
swfdec_as_context_remove_strings (gpointer key, gpointer value, gpointer data)
254
SwfdecAsContext *context = data;
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);
262
} else if (string[0] & SWFDEC_AS_GC_MARK) {
263
SWFDEC_LOG ("marked: %s", (char *) key);
264
string[0] &= ~SWFDEC_AS_GC_MARK;
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);
277
swfdec_as_context_remove_objects (gpointer key, gpointer value, gpointer debugger)
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);
289
SWFDEC_LOG ("deleted: %s %p", G_OBJECT_TYPE_NAME (gc), gc);
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.
315
swfdec_as_string_mark (const char *string)
319
g_return_if_fail (string != NULL);
321
str = (char *) string - 1;
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.
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));
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? */
357
swfdec_as_context_mark_constant_pools (gpointer key, gpointer value, gpointer unused)
359
SwfdecConstantPool *pool = value;
362
for (i = 0; i < swfdec_constant_pool_size (pool); i++) {
363
const char *s = swfdec_constant_pool_get (pool, i);
365
swfdec_as_string_mark (s);
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
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)
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;
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
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 ***/
455
G_DEFINE_TYPE (SwfdecAsContext, swfdec_as_context, G_TYPE_OBJECT)
456
static guint signals[LAST_SIGNAL] = { 0, };
459
swfdec_as_context_get_property (GObject *object, guint param_id, GValue *value,
462
SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
466
g_value_set_object (value, context->debugger);
469
g_value_set_boolean (value, context->state == SWFDEC_AS_CONTEXT_ABORTED);
472
g_value_set_ulong (value, (gulong) context->memory_until_gc);
475
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
481
swfdec_as_context_set_property (GObject *object, guint param_id, const GValue *value,
484
SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
488
context->debugger = SWFDEC_AS_DEBUGGER (g_value_dup_object (value));
490
case PROP_RANDOM_SEED:
491
g_rand_set_seed (context->rand, g_value_get_uint (value));
494
context->memory_until_gc = g_value_get_ulong (value);
497
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
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);
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;
570
swfdec_as_context_init (SwfdecAsContext *context)
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);
584
context->rand = g_rand_new ();
585
g_get_current_time (&context->start_time);
591
swfdec_as_context_create_string (SwfdecAsContext *context, const char *string, gsize len)
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);
603
new[0] = 0; /* GC flags */
604
g_hash_table_insert (context->strings, new + 1, new);
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
618
* Returns: the garbage-collected version of @string
621
swfdec_as_context_get_string (SwfdecAsContext *context, const char *string)
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))
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
648
swfdec_as_context_give_string (SwfdecAsContext *context, char *string)
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);
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
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);
694
* Returns: the currently executing frame or %NULL if none
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.
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
731
* Returns: %TRUE if an exception was catched, %FALSE otherwise
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)
742
*value = context->exception_value;
744
context->exception = FALSE;
745
SWFDEC_AS_VALUE_SET_UNDEFINED (&context->exception_value);
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.
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);
769
klass->get_time (context, tv);
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
783
* <note>A lot of convenience functions like swfdec_as_object_run() call this
784
* function automatically.</note>
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;
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() */
808
frame = context->frame;
809
original_version = context->version;
812
if (context->state == SWFDEC_AS_CONTEXT_ABORTED)
814
if (!swfdec_as_context_check_continue (context))
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");
822
if (context->debugger) {
823
SwfdecAsDebuggerClass *klass = SWFDEC_AS_DEBUGGER_GET_CLASS (context->debugger);
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;
838
while (context->state < SWFDEC_AS_CONTEXT_ABORTED) {
839
if (context->exception) {
840
swfdec_as_frame_handle_exception (frame);
841
if (frame != context->frame)
847
swfdec_as_frame_return (frame, NULL);
850
if (pc < startpc || pc >= endpc) {
851
SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc, startpc, endpc);
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);
858
if (frame != context->frame)
860
if (context->exception)
863
if (context->exception)
866
/* decode next action */
868
/* invoke debugger if there is one */
871
(* step) (context->debugger, context);
872
if (frame != context->frame)
878
spec = swfdec_as_actions + action;
880
if (pc + 2 >= endpc) {
881
SWFDEC_ERROR ("action %u length value out of range", action);
885
len = pc[1] | pc[2] << 8;
886
if (data + len > endpc) {
887
SWFDEC_ERROR ("action %u length %u out of range", action, len);
890
nextpc = pc + 3 + len;
896
/* check action is valid */
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;
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);
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");
920
#ifndef G_DISABLE_ASSERT
921
check = (spec->add >= 0 && spec->remove >= 0) ? context->cur + spec->add - spec->remove : NULL;
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;
931
if (frame->pc < pc &&
932
!swfdec_as_context_check_continue (context)) {
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);
947
/* someone called/returned from a function, reread variables */
953
if (context->frame == frame)
954
swfdec_as_frame_return (frame, NULL);
956
context->version = original_version;
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]);
971
swfdec_as_context_ASSetPropFlags_foreach (SwfdecAsObject *object,
972
const char *s, SwfdecAsValue *val, guint cur_flags, gpointer data)
976
/* shortcut if the flags already match */
977
if (cur_flags == ((cur_flags &~ flags[1]) | flags[0]))
980
swfdec_as_context_ASSetPropFlags_set_one_flag (object, s, flags);
984
SWFDEC_AS_NATIVE (1, 0, swfdec_as_context_ASSetPropFlags)
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 */
995
if (!SWFDEC_AS_VALUE_IS_OBJECT (&argv[0]))
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");
1007
if (SWFDEC_AS_VALUE_IS_NULL (&argv[1])) {
1008
swfdec_as_object_foreach (obj, swfdec_as_context_ASSetPropFlags_foreach, flags);
1011
g_strsplit (swfdec_as_value_to_string (cx, &argv[1]), ",", -1);
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);
1021
SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite)
1023
swfdec_as_context_isFinite (SwfdecAsContext *cx, SwfdecAsObject *object,
1024
guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
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)
1037
swfdec_as_context_isNaN (SwfdecAsContext *cx, SwfdecAsObject *object,
1038
guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
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)
1051
swfdec_as_context_parseInt (SwfdecAsContext *cx, SwfdecAsObject *object,
1052
guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
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);
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);
1075
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
1077
} else if ((s[0] == '0' || ((s[0] == '+' || s[0] == '-') && s[1] == '0')) &&
1078
s[strspn (s+1, "01234567") + 1] == '\0') {
1085
// skip 0x at the start
1086
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
1089
// strtoll parses strings with 0x when given radix 16, but we don't want that
1091
const char *skip = s + strspn (s, " \t\r\n");
1092
if (skip != s && (skip[0] == '-' || skip[0] == '+'))
1094
if (skip != s && skip[0] == '0' && (skip[1] == 'x' || skip[1] == 'X')) {
1095
SWFDEC_AS_VALUE_SET_NUMBER (retval, 0);
1100
i = g_ascii_strtoll (s, &tail, radix);
1103
SWFDEC_AS_VALUE_SET_NUMBER (retval, NAN);
1107
if (i > G_MAXINT32 || i < G_MININT32) {
1108
SWFDEC_AS_VALUE_SET_NUMBER (retval, i);
1110
SWFDEC_AS_VALUE_SET_INT (retval, i);
1114
SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat)
1116
swfdec_as_context_parseFloat (SwfdecAsContext *cx, SwfdecAsObject *object,
1117
guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
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) {
1132
d = g_ascii_strtod (s, &tail);
1135
SWFDEC_AS_VALUE_SET_NUMBER (retval, NAN);
1137
SWFDEC_AS_VALUE_SET_NUMBER (retval, d);
1144
swfdec_as_context_init_global (SwfdecAsContext *context)
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);
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);
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");
1171
swfdec_as_object_run (context->global, script);
1172
swfdec_script_unref (script);
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.
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))
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.
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)
1230
if (!klass->check_continue (context)) {
1231
swfdec_as_context_abort (context, "Runtime exceeded");
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.
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;