implement breakpoint API (breakpoints don't actually trigger yet)
[swfdec.git] / libswfdec / actions.c
blobb65ae55f08727e974a0b7050b270854dd5f2b50a
1 #include <ctype.h>
2 #include <string.h>
4 #include "swfdec_internal.h"
5 #include "js/jsfun.h"
7 void
8 constant_pool_ref (ConstantPool *pool)
10 pool->refcount++;
13 void
14 constant_pool_unref (ConstantPool *pool)
16 if (--pool->refcount == 0) {
17 int i;
19 for (i=0;i<pool->n_constants;i++){
20 g_free(pool->constants[i]);
22 g_free (pool->constants);
23 g_free (pool);
27 int
28 pc_is_valid (SwfdecActionContext *context, unsigned char *pc)
30 unsigned char *startpc;
31 unsigned char *endpc;
33 startpc = context->bits.buffer->data;
34 endpc = context->bits.buffer->data + context->bits.buffer->length;
35 /* Use a <= here because sometimes you'll get a jump to endpc in functions as
36 * a way of branching to return (without an ActionReturn). action_script_call
37 * will handle making the return happen.
39 if (pc >= startpc && pc <= endpc) {
40 return SWF_OK;
41 } else {
42 return SWF_ERROR;
46 void swfdec_init_context (SwfdecDecoder * s)
48 SwfdecActionContext *context;
49 int i;
50 jsval val;
52 context = g_malloc0 (sizeof(SwfdecActionContext));
53 s->context = context;
54 context->s = s;
55 context->call_stack = g_queue_new();
57 context->jsrt = JS_NewRuntime (64L * 1024L * 1024L);
58 if (!context->jsrt)
59 return;
61 context->jscx = JS_NewContext (context->jsrt, 8192);
63 JS_SetContextPrivate (context->jscx, context);
65 swfdec_init_context_builtins (context);
67 context->stack = JS_NewArrayObject (context->jscx, 0, NULL);
68 JS_AddRoot (context->jscx, &context->stack);
69 context->stack_top = 0;
71 context->registers = JS_NewArrayObject (context->jscx, 0, NULL);
72 JS_AddRoot (context->jscx, &context->registers);
73 for (i = 0; i < 4; i++) {
74 val = JSVAL_VOID;
75 JS_SetElement (context->jscx, context->registers, i, &val);
78 #if SWFDEC_ACTIONS_DEBUG_GC
79 JS_GC(context->jscx);
80 #endif
83 JSBool
84 action_script_call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
85 jsval *rval)
87 int action;
88 int len;
89 ActionFuncEntry *func_entry;
90 CallFrame *frame;
91 SwfdecActionContext *context;
92 JSFunction *jsfunc;
93 ScriptFunction *func;
95 /* argv[-2] is funobj, argc[-1] is this, argv[0] through argv[argc-1] are the
96 * args, and beyond that are 3 tag temporaries and then the array object for
97 * registers.
100 context = JS_GetContextPrivate (cx);
101 jsfunc = JS_ValueToFunction (cx, argv[-2]);
102 func = jsfunc->priv;
104 /* FIXME: set limits on recursion to prevent stack exhaustion */
106 /* Native methods short circuit this whole mess. */
107 if (func->meth) {
108 return func->meth (context, argc);
111 frame = g_malloc0 (sizeof(CallFrame));
112 frame->returnpc = context->pc;
113 frame->is_function2 = func->is_function2;
114 frame->bits.buffer = func->buffer;
115 frame->bits.ptr = func->pc;
116 frame->bits.idx = 0;
117 frame->bits.end = func->pc + func->code_size;
118 frame->returnbuffer = context->bits.buffer;
119 frame->returnpool = context->constantpool;
120 frame->returnargv = context->tag_argv;
121 frame->returnargc = context->tag_argc;
122 context->constantpool = func->constantpool;
123 if (func->constantpool)
124 constant_pool_ref(func->constantpool);
125 context->pc = func->pc;
126 context->tag_argv = argv;
127 context->tag_argc = argc;
128 g_queue_push_head (context->call_stack, frame);
129 context->call_depth++;
131 if (func->is_function2) {
132 int reg;
133 unsigned int i;
135 frame->registers = JS_NewArrayObject (context->jscx, 0, NULL);
136 argv[argc + 3] = OBJECT_TO_JSVAL(frame->registers);
138 reg = 1;
139 if (func->preload_this) {
140 jsval val = OBJECT_TO_JSVAL(obj);
141 JS_SetElement (context->jscx, frame->registers, reg++, &val);
143 /* FIXME: load arguments */
144 if (func->preload_super) {
145 jsval val = OBJECT_TO_JSVAL(JS_GetConstructor (context->jscx,
146 JS_GetParent (context->jscx, obj)));
147 JS_SetElement (context->jscx, frame->registers, reg++, &val);
149 if (func->preload_root) {
150 jsval val = OBJECT_TO_JSVAL(context->root);
151 JS_SetElement (context->jscx, frame->registers, reg++, &val);
153 if (func->preload_parent) {
154 jsval val = OBJECT_TO_JSVAL(JS_GetParent (context->jscx, obj));
155 JS_SetElement (context->jscx, frame->registers, reg++, &val);
157 if (func->preload_global) {
158 jsval val = OBJECT_TO_JSVAL(context->global);
159 JS_SetElement (context->jscx, frame->registers, reg++, &val);
162 for (i = 0; (i < argc) && (i < func->num_params); i++) {
163 if (func->param_regs[i]) {
164 JS_SetElement (context->jscx, frame->registers, func->param_regs[i],
165 &argv[i]);
166 } else {
167 SWFDEC_WARNING ("named arguments unsupported");
172 context->bits.buffer = frame->bits.buffer; /* for pc_is_valid () */
174 while (context->pc < frame->bits.end) {
175 #if SWFDEC_ACTIONS_DEBUG_GC
176 JS_GC(context->jscx);
177 #endif
179 action = swfdec_bits_get_u8 (&frame->bits);
180 if (action & 0x80) {
181 len = swfdec_bits_get_u16 (&frame->bits);
182 context->bits.buffer = frame->bits.buffer;
183 context->bits.ptr = frame->bits.ptr;
184 context->bits.idx = 0;
185 context->bits.end = frame->bits.ptr + len;
186 } else {
187 len = 0;
189 frame->bits.ptr += len;
190 context->pc = frame->bits.ptr;
191 context->action = action;
193 func_entry = get_action (action);
195 if (context->skip > 0) {
196 SWFDEC_DEBUG ("[skip] depth %d, action 0x%02x (%s)", context->call_depth,
197 action, func_entry ? func_entry->name : "(unknown)");
198 context->skip--;
199 continue;
202 SWFDEC_DEBUG ("depth %d, action 0x%02x (%s)", context->call_depth,
203 action, func_entry ? func_entry->name : "(unknown)");
204 if (func_entry) {
205 func_entry->func (context);
206 } else {
207 SWFDEC_WARNING ("unknown action 0x%02x, ignoring", action);
208 context->error = 1;
211 if (len) {
212 if (context->bits.ptr < context->bits.end) {
213 SWFDEC_ERROR ("action 0x%02x didn't read all data (%d < %d)",
214 action,
215 context->bits.ptr - (frame->bits.ptr - len),
216 context->bits.end - (frame->bits.ptr - len));
218 if (context->bits.ptr > context->bits.end) {
219 SWFDEC_ERROR ("action 0x%02x read past end of buffer (%d > %d)",
220 action,
221 context->bits.ptr - (frame->bits.ptr - len),
222 context->bits.end - (frame->bits.ptr - len));
226 if (frame->done)
227 break;
229 frame->bits.ptr = context->pc;
231 if (context->error) {
232 context->error = 0;
233 SWFDEC_ERROR ("action script error (see warnings)");
237 SWFDEC_DEBUG("returning from actionscript call");
239 *rval = stack_pop (context);
241 if (context->constantpool) {
242 constant_pool_unref (context->constantpool);
244 context->constantpool = frame->returnpool;
245 context->bits.buffer = frame->returnbuffer;
246 context->pc = frame->returnpc;
247 context->tag_argv = frame->returnargv;
248 context->tag_argc = frame->returnargc;
249 g_queue_pop_head (context->call_stack);
250 context->call_depth--;
251 g_free (frame);
253 return 1;
257 swfdec_action_script_execute (SwfdecDecoder * s, SwfdecBuffer * buffer)
259 SwfdecActionContext *context;
260 jsval rval;
261 JSFunction *jsfunc;
262 ScriptFunction *func;
264 if (!s->parse_sprite) {
265 s->parse_sprite = s->main_sprite;
266 s->parse_sprite_seg = s->main_sprite_seg;
269 SWFDEC_INFO ("swfdec_action_script_execute (sprite %d) %p %p %d",
270 SWFDEC_OBJECT(s->parse_sprite)->id, buffer, buffer->data, buffer->length);
272 if (s->context == NULL)
273 swfdec_init_context (s);
274 context = s->context;
276 #if SWFDEC_ACTIONS_DEBUG_GC
277 JS_GC(context->jscx);
278 #endif
280 func = g_malloc0 (sizeof (ScriptFunction));
281 if (func == NULL) {
282 SWFDEC_ERROR ("out of memory");
283 return SWF_ERROR;
286 func->num_params = 0;
287 func->code_size = buffer->length;
288 func->pc = buffer->data;
289 func->buffer = buffer;
291 jsfunc = JS_NewFunction (context->jscx, action_script_call, func->num_params,
292 0, context->global, "");
293 jsfunc->priv = func;
295 JS_CallFunction (context->jscx, context->root, jsfunc, 0, NULL, &rval);
297 /* FIXME: Deallocate the function? */
299 /* FIXME: as of Flash 6/7, stack gets emptied at the end of every action
300 * block. -- flasm.sf.net
302 return SWF_OK;
305 /* stack ops */
307 jsval
308 stack_pop (SwfdecActionContext *context)
310 JSBool ok;
311 jsval val;
313 if (context->stack_top == 0)
314 return JSVAL_VOID;
316 ok = JS_GetElement (context->jscx, context->stack, --context->stack_top,
317 &val);
319 if (!ok) {
320 SWFDEC_WARNING("Couldn't pop element");
321 return JSVAL_VOID;
324 return val;
327 void
328 stack_push (SwfdecActionContext *context, jsval val)
330 JSBool ok;
332 #if SWFDEC_ACTIONS_DEBUG_GC
333 JS_GC(context->jscx);
334 #endif
336 ok = JS_SetElement (context->jscx, context->stack, context->stack_top++,
337 &val);
339 if (!ok)
340 SWFDEC_WARNING("Couldn't push element");
343 char *
344 name_object (SwfdecActionContext *context, JSObject *obj)
346 /* FIXME: I envision this function walking the tree from _global, trying to
347 * find a name for the object that's on the stack. That might be
348 * unreasonable. For now, identify if the object is _global.
351 if (obj == context->global) {
352 return g_strdup ("_global");
353 } else if (obj == context->root) {
354 return g_strdup ("_root");
355 } else {
356 char *name = g_malloc (20);
357 g_snprintf(name, 20, "%p", obj);
358 return name;
362 void
363 stack_show (SwfdecActionContext *context)
365 jsval val;
366 int i = 0;
368 SWFDEC_INFO ("Stack:");
369 for (i = context->stack_top - 1; i >= 0; i--) {
370 JS_GetElement (context->jscx, context->stack, i, &val);
371 if (JSVAL_IS_NULL(val)) {
372 SWFDEC_INFO (" %d: (null)", i);
373 } else if (JSVAL_IS_VOID(val)) {
374 SWFDEC_INFO (" %d: (undefined)", i);
375 } else if (JSVAL_IS_STRING(val)) {
376 SWFDEC_INFO (" %d: \"%s\"", i, JS_GetStringBytes(JSVAL_TO_STRING (val)));
377 } else if (JSVAL_IS_INT(val)) {
378 SWFDEC_INFO (" %d: %d", i, JSVAL_TO_INT (val));
379 } else if (JSVAL_IS_DOUBLE(val)) {
380 SWFDEC_INFO (" %d: %g", i, *JSVAL_TO_DOUBLE (val));
381 } else if (JSVAL_IS_BOOLEAN(val)) {
382 SWFDEC_INFO (" %d: %s", i, JSVAL_TO_BOOLEAN (val) ? "true" : "false");
383 } else if (JSVAL_IS_OBJECT(val)) {
384 char *name = name_object (context, JSVAL_TO_OBJECT(val));
385 SWFDEC_INFO (" %d: obj (%s)", i, name);
386 g_free (name);
387 } else {
388 SWFDEC_INFO (" %d: unknown type", i);
393 void
394 stack_show_value (SwfdecActionContext *context, jsval val)
396 int i = 0;
398 if (JSVAL_IS_NULL(val)) {
399 SWFDEC_INFO (" %d: (null)", i);
400 } else if (JSVAL_IS_VOID(val)) {
401 SWFDEC_INFO (" %d: (undefined)", i);
402 } else if (JSVAL_IS_STRING(val)) {
403 SWFDEC_INFO (" %d: \"%s\"", i, JS_GetStringBytes(JSVAL_TO_STRING (val)));
404 } else if (JSVAL_IS_INT(val)) {
405 SWFDEC_INFO (" %d: %d", i, JSVAL_TO_INT (val));
406 } else if (JSVAL_IS_DOUBLE(val)) {
407 SWFDEC_INFO (" %d: %g", i, *JSVAL_TO_DOUBLE (val));
408 } else if (JSVAL_IS_BOOLEAN(val)) {
409 SWFDEC_INFO (" %d: %s", i, JSVAL_TO_BOOLEAN (val) ? "true" : "false");
410 } else if (JSVAL_IS_OBJECT(val)) {
411 char *name = name_object (context, JSVAL_TO_OBJECT(val));
412 SWFDEC_INFO (" %d: obj (%s)", i, name);
413 g_free (name);
414 } else {
415 SWFDEC_INFO (" %d: unknown type", i);
419 /* Given a jsval, convert it into an object. JS_ValueToObject appears to have
420 * some side effect when working on an existing object, at least. While I'm
421 * here, make it print out when we actually convert a non-object to object.
422 * Perhaps this never occurs in actionscript bytecode.
424 JSObject *
425 jsval_as_object (SwfdecActionContext *context, jsval val)
427 if (JSVAL_IS_OBJECT(val)) {
428 return JSVAL_TO_OBJECT(val);
429 } else {
430 JSObject *obj = NULL;
431 JSBool ok;
433 SWFDEC_INFO("Converting value 0x%x to object", val);
434 ok = JS_ValueToObject (context->jscx, val, &obj);
435 if (!ok) {
436 SWFDEC_ERROR("Couldn't convert value %x to object", val);
438 return obj;