|
|
/*
|
|
|
* Common parts for the JavaScript virtual machine.
|
|
|
* Copyright (c) 1998 New Generation Software (NGS) Oy
|
|
|
*
|
|
|
* Author: Markku Rossi <mtr@ngs.fi>
|
|
|
*/
|
|
|
|
|
|
/*
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
* modify it under the terms of the GNU Library General Public
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
* version 2 of the License, or (at your option) any later version.
|
|
|
*
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
* Library General Public License for more details.
|
|
|
*
|
|
|
* You should have received a copy of the GNU Library General Public
|
|
|
* License along with this library; if not, write to the Free
|
|
|
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
|
|
* MA 02111-1307, USA
|
|
|
*/
|
|
|
|
|
|
/*
|
|
|
* $Source: /tmp/cvsroot-15-2-2007/clamav-devel/libclamav/js/vm.c,v $
|
|
|
* $Id: vm.c,v 1.2 2006/10/28 11:27:44 njh Exp $
|
|
|
*/
|
|
|
#if HAVE_CONFIG_H
|
|
|
#include "clamav-config.h"
|
|
|
#endif
|
|
|
|
|
|
#ifdef CL_EXPERIMENTAL
|
|
|
|
|
|
#include "jsint.h"
|
|
|
|
|
|
/*
|
|
|
* Types and definitions.
|
|
|
*/
|
|
|
|
|
|
/*
|
|
|
* Collect garbage if we allocated more than GC_TRIGGER bytes of
|
|
|
* memory since the last gc.
|
|
|
*/
|
|
|
#if SIZEOF_INT == 2
|
|
|
#define GC_TRIGGER (1L * 1024L * 1024L)
|
|
|
#else
|
|
|
#define GC_TRIGGER (2 * 1024 * 1024)
|
|
|
#endif
|
|
|
|
|
|
/*
|
|
|
* Prototypes for static functions.
|
|
|
*/
|
|
|
|
|
|
static void intern_builtins (JSVirtualMachine *vm);
|
|
|
|
|
|
|
|
|
/*
|
|
|
* Global functions.
|
|
|
*/
|
|
|
|
|
|
JSVirtualMachine *
|
|
|
js_vm_create (unsigned int stack_size, JSVMDispatchMethod dispatch_method,
|
|
|
unsigned int verbose, int stacktrace_on_error,
|
|
|
JSIOStream *s_stdin, JSIOStream *s_stdout, JSIOStream *s_stderr)
|
|
|
{
|
|
|
JSVirtualMachine *vm;
|
|
|
|
|
|
vm = js_calloc (NULL, 1, sizeof (*vm));
|
|
|
if (vm == NULL)
|
|
|
return NULL;
|
|
|
|
|
|
vm->verbose = verbose;
|
|
|
vm->stacktrace_on_error = stacktrace_on_error;
|
|
|
vm->warn_undef = 1;
|
|
|
|
|
|
/* Set the system streams. */
|
|
|
vm->s_stdin = s_stdin;
|
|
|
vm->s_stdout = s_stdout;
|
|
|
vm->s_stderr = s_stderr;
|
|
|
|
|
|
/* Resolve the dispatch method. */
|
|
|
switch (dispatch_method)
|
|
|
{
|
|
|
case JS_VM_DISPATCH_SWITCH_BASIC:
|
|
|
#if ALL_DISPATCHERS
|
|
|
vm->dispatch_method = dispatch_method;
|
|
|
vm->dispatch_method_name = "switch-basic";
|
|
|
vm->dispatch_execute = js_vm_switch0_exec;
|
|
|
vm->dispatch_func_name = js_vm_switch0_func_name;
|
|
|
vm->dispatch_debug_position = js_vm_switch0_debug_position;
|
|
|
#endif
|
|
|
break;
|
|
|
|
|
|
case JS_VM_DISPATCH_JUMPS:
|
|
|
#if __GNUC__ && !DISABLE_JUMPS
|
|
|
vm->dispatch_method = dispatch_method;
|
|
|
vm->dispatch_method_name = "jumps";
|
|
|
vm->dispatch_execute = js_vm_jumps_exec;
|
|
|
vm->dispatch_func_name = js_vm_jumps_func_name;
|
|
|
vm->dispatch_debug_position = js_vm_jumps_debug_position;
|
|
|
#endif /* not (__GNUC__ && !DISABLE_JUMPS) */
|
|
|
break;
|
|
|
|
|
|
case JS_VM_DISPATCH_SWITCH:
|
|
|
/* This is the default, let the default catcher handle us. */
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (vm->dispatch_execute == NULL)
|
|
|
{
|
|
|
/* Set the default that is the optimized switch. */
|
|
|
vm->dispatch_method = JS_VM_DISPATCH_SWITCH;
|
|
|
vm->dispatch_method_name = "switch";
|
|
|
vm->dispatch_execute = js_vm_switch_exec;
|
|
|
vm->dispatch_func_name = js_vm_switch_func_name;
|
|
|
vm->dispatch_debug_position = js_vm_switch_debug_position;
|
|
|
}
|
|
|
|
|
|
vm->stack_size = stack_size;
|
|
|
vm->stack = js_malloc (NULL, vm->stack_size * sizeof (*vm->stack));
|
|
|
if (vm->stack == NULL)
|
|
|
{
|
|
|
js_free (vm);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
/* Set the initial stack pointer. */
|
|
|
vm->sp = vm->stack + vm->stack_size - 1;
|
|
|
|
|
|
vm->gc.trigger = GC_TRIGGER;
|
|
|
|
|
|
/* We need a toplevel here. */
|
|
|
{
|
|
|
JSErrorHandlerFrame handler;
|
|
|
int result = 1;
|
|
|
|
|
|
memset (&handler, 0, sizeof (handler));
|
|
|
handler.next = vm->error_handler;
|
|
|
vm->error_handler = &handler;
|
|
|
|
|
|
if (setjmp (vm->error_handler->error_jmp))
|
|
|
/* An error occurred. */
|
|
|
result = 0;
|
|
|
else
|
|
|
{
|
|
|
/* Intern some commonly used symbols. */
|
|
|
vm->syms.s___proto__ = js_vm_intern (vm, "__proto__");
|
|
|
vm->syms.s_prototype = js_vm_intern (vm, "prototype");
|
|
|
vm->syms.s_toSource = js_vm_intern (vm, "toSource");
|
|
|
vm->syms.s_toString = js_vm_intern (vm, "toString");
|
|
|
vm->syms.s_valueOf = js_vm_intern (vm, "valueOf");
|
|
|
|
|
|
/* Intern system built-in objects. */
|
|
|
intern_builtins (vm);
|
|
|
}
|
|
|
|
|
|
/* Pop the error handler. */
|
|
|
vm->error_handler = vm->error_handler->next;
|
|
|
|
|
|
if (result == 0)
|
|
|
{
|
|
|
/* Argh, the initialization failed. */
|
|
|
js_vm_destroy (vm);
|
|
|
return NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return vm;
|
|
|
}
|
|
|
|
|
|
|
|
|
void
|
|
|
js_vm_destroy (JSVirtualMachine *vm)
|
|
|
{
|
|
|
int i;
|
|
|
JSHeapBlock *hb, *hb2;
|
|
|
JSErrorHandlerFrame *f, *f2;
|
|
|
JSHashBucket *hashb, *hashb_next;
|
|
|
|
|
|
/* Free all objects from the heap. */
|
|
|
js_vm_clear_heap (vm);
|
|
|
|
|
|
/* Free the constants. */
|
|
|
|
|
|
for (i = 0; i < vm->num_consts; i++)
|
|
|
if (vm->consts[i].type == JS_STRING)
|
|
|
js_free (vm->consts[i].u.vstring->data);
|
|
|
js_free (vm->consts);
|
|
|
|
|
|
/* Free the globals. */
|
|
|
|
|
|
for (i = 0; i < JS_HASH_TABLE_SIZE; i++)
|
|
|
for (hashb = vm->globals_hash[i]; hashb; hashb = hashb_next)
|
|
|
{
|
|
|
hashb_next = hashb->next;
|
|
|
js_free (hashb->name);
|
|
|
js_free (hashb);
|
|
|
}
|
|
|
js_free (vm->globals);
|
|
|
|
|
|
/* Stack. */
|
|
|
js_free (vm->stack);
|
|
|
|
|
|
/* Heap blocks. */
|
|
|
for (hb = vm->heap; hb; hb = hb2)
|
|
|
{
|
|
|
hb2 = hb->next;
|
|
|
js_free (hb);
|
|
|
}
|
|
|
|
|
|
/* Error handlers. */
|
|
|
for (f = vm->error_handler; f; f = f2)
|
|
|
{
|
|
|
f2 = f->next;
|
|
|
js_free (f);
|
|
|
}
|
|
|
|
|
|
#if PROFILING
|
|
|
#define NUM_OPS 68
|
|
|
|
|
|
/* Dump profiling data to the stderr. */
|
|
|
{
|
|
|
unsigned int total = 0;
|
|
|
int i;
|
|
|
|
|
|
/* Count total interrupts. */
|
|
|
for (i = 0; i <= NUM_OPS; i++)
|
|
|
total += vm->prof_count[i];
|
|
|
|
|
|
/* Dump individual statistics. */
|
|
|
for (i = 0; i <= NUM_OPS; i++)
|
|
|
{
|
|
|
char buf[512];
|
|
|
|
|
|
sprintf (buf, "%d\t%u\t%.2f%s",
|
|
|
i, vm->prof_count[i],
|
|
|
(double) vm->prof_count[i] / total * 100,
|
|
|
JS_HOST_LINE_BREAK);
|
|
|
|
|
|
js_iostream_write (vm->s_stderr);
|
|
|
}
|
|
|
}
|
|
|
#endif /* PROFILING */
|
|
|
|
|
|
/* Flush and free the default system streams. */
|
|
|
|
|
|
js_iostream_close (vm->s_stdin);
|
|
|
js_iostream_close (vm->s_stdout);
|
|
|
js_iostream_close (vm->s_stderr);
|
|
|
|
|
|
/* And finally, the VM handle. */
|
|
|
js_free (vm);
|
|
|
}
|
|
|
|
|
|
#if PROFILING
|
|
|
/*
|
|
|
* The support stuffs for the byte-code operand profiling.
|
|
|
*/
|
|
|
|
|
|
static JSVirtualMachine *profiling_vm = NULL;
|
|
|
|
|
|
static void
|
|
|
sig_alarm (int sig)
|
|
|
{
|
|
|
if (profiling_vm && profiling_vm->prof_op < 100)
|
|
|
profiling_vm->prof_count[profiling_vm->prof_op]++;
|
|
|
|
|
|
signal (sig, sig_alarm);
|
|
|
}
|
|
|
|
|
|
/* Turn on the byte-code operand profiling. */
|
|
|
#define PROFILING_ON() \
|
|
|
profiling_vm = vm; \
|
|
|
vm->prof_op = 255; \
|
|
|
signal (SIGALRM, sig_alarm); \
|
|
|
ualarm (1, 1)
|
|
|
|
|
|
/* Turn off the byte-code operand profiling. */
|
|
|
#define PROFILING_OFF() \
|
|
|
vm->prof_op = 255; \
|
|
|
ualarm (0, 0); \
|
|
|
signal (SIGALRM, SIG_IGN); \
|
|
|
profiling_vm = NULL
|
|
|
|
|
|
#else /* not PROFILING */
|
|
|
|
|
|
#define PROFILING_ON()
|
|
|
#define PROFILING_OFF()
|
|
|
|
|
|
#endif /* not PROFILING */
|
|
|
|
|
|
int
|
|
|
js_vm_execute (JSVirtualMachine *vm, JSByteCode *bc)
|
|
|
{
|
|
|
int i, sect;
|
|
|
unsigned int ui;
|
|
|
unsigned char *cp;
|
|
|
unsigned int consts_offset;
|
|
|
char buf[256];
|
|
|
JSSymtabEntry *symtab = NULL;
|
|
|
unsigned int num_symtab_entries = 0;
|
|
|
unsigned int code_len = 0;
|
|
|
JSNode *saved_sp;
|
|
|
JSErrorHandlerFrame *handler, *saved_handler;
|
|
|
int result = 1;
|
|
|
unsigned char *debug_info;
|
|
|
unsigned int debug_info_len;
|
|
|
unsigned int anonymous_function_offset;
|
|
|
|
|
|
/* We need a toplevel over the whole function. */
|
|
|
|
|
|
saved_sp = vm->sp;
|
|
|
saved_handler = vm->error_handler;
|
|
|
|
|
|
handler = js_calloc (NULL, 1, sizeof (*handler));
|
|
|
if (handler == NULL)
|
|
|
{
|
|
|
sprintf (vm->error, "VM: out of memory");
|
|
|
return 0;
|
|
|
}
|
|
|
handler->next = vm->error_handler;
|
|
|
vm->error_handler = handler;
|
|
|
|
|
|
if (setjmp (vm->error_handler->error_jmp))
|
|
|
{
|
|
|
/* Ok, we had an error down there somewhere. */
|
|
|
result = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* The main stuffs for the execute. */
|
|
|
|
|
|
/* Process constants. */
|
|
|
consts_offset = vm->num_consts;
|
|
|
anonymous_function_offset = vm->anonymous_function_next_id;
|
|
|
|
|
|
for (sect = 0; sect < bc->num_sects; sect++)
|
|
|
if (bc->sects[sect].type == JS_BCST_CONSTANTS)
|
|
|
{
|
|
|
cp = bc->sects[sect].data;
|
|
|
|
|
|
for (ui = 0; ui < bc->sects[sect].length;)
|
|
|
{
|
|
|
JSNode *c;
|
|
|
|
|
|
/* Check that we still have space for this constant. */
|
|
|
if (vm->num_consts >= vm->consts_alloc)
|
|
|
{
|
|
|
vm->consts = js_realloc (vm, vm->consts,
|
|
|
(vm->consts_alloc + 1024)
|
|
|
* sizeof (JSNode));
|
|
|
vm->consts_alloc += 1024;
|
|
|
}
|
|
|
c = &vm->consts[vm->num_consts++];
|
|
|
|
|
|
/* Process this constant. */
|
|
|
c->type = (JSNodeType) cp[ui++];
|
|
|
switch (c->type)
|
|
|
{
|
|
|
case JS_NULL:
|
|
|
break;
|
|
|
|
|
|
case JS_BOOLEAN:
|
|
|
c->u.vboolean = cp[ui++];
|
|
|
break;
|
|
|
|
|
|
case JS_STRING:
|
|
|
c->u.vstring = js_vm_alloc (vm, sizeof (*c->u.vstring));
|
|
|
c->u.vstring->staticp = 1;
|
|
|
c->u.vstring->prototype = NULL;
|
|
|
|
|
|
JS_BC_READ_INT32 (cp + ui, c->u.vstring->len);
|
|
|
ui += 4;
|
|
|
|
|
|
c->u.vstring->data = js_malloc (vm, c->u.vstring->len + 1);
|
|
|
memcpy (c->u.vstring->data, cp + ui, c->u.vstring->len);
|
|
|
c->u.vstring->data[c->u.vstring->len] = '\0';
|
|
|
|
|
|
ui += c->u.vstring->len;
|
|
|
break;
|
|
|
|
|
|
case JS_INTEGER:
|
|
|
JS_BC_READ_INT32 (cp + ui, c->u.vinteger);
|
|
|
ui += 4;
|
|
|
break;
|
|
|
|
|
|
case JS_FLOAT:
|
|
|
memcpy (&c->u.vfloat, cp + ui, 8);
|
|
|
ui += 8;
|
|
|
break;
|
|
|
|
|
|
case JS_SYMBOL:
|
|
|
for (i = 0; cp[ui]; ui++, i++)
|
|
|
buf[i] = cp[ui];
|
|
|
buf[i] = '\0';
|
|
|
|
|
|
/* Eat the trailing '\0' from the data. */
|
|
|
ui++;
|
|
|
|
|
|
if (buf[0] == '.' && buf[1] == 'F' && buf[2] == ':')
|
|
|
sprintf (buf + 3, "%u",
|
|
|
vm->anonymous_function_next_id++);
|
|
|
|
|
|
/* Intern symbol. */
|
|
|
c->u.vsymbol = js_vm_intern (vm, buf);
|
|
|
break;
|
|
|
|
|
|
case JS_BUILTIN:
|
|
|
/* Regular expression. */
|
|
|
{
|
|
|
unsigned char flags;
|
|
|
unsigned int length;
|
|
|
|
|
|
flags = cp[ui++];
|
|
|
|
|
|
JS_BC_READ_INT32 (cp + ui, length);
|
|
|
ui += 4;
|
|
|
|
|
|
js_builtin_RegExp_new (vm, cp + ui, length, flags, 1,
|
|
|
NULL, c);
|
|
|
ui += length;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case JS_NAN:
|
|
|
/* Nothing here. */
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
case JS_IPTR:
|
|
|
sprintf (buf,
|
|
|
"js_vm_execute(): unknown constant type %d%s",
|
|
|
c->type,
|
|
|
JS_HOST_LINE_BREAK);
|
|
|
|
|
|
js_iostream_write (vm->s_stderr, buf, strlen (buf));
|
|
|
js_iostream_flush (vm->s_stderr);
|
|
|
|
|
|
abort ();
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* All done with the constants. */
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
/* Check how long the code section is. */
|
|
|
for (sect = 0; sect < bc->num_sects; sect++)
|
|
|
if (bc->sects[sect].type == JS_BCST_CODE)
|
|
|
{
|
|
|
code_len = bc->sects[sect].length;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
/* Process symbol table. */
|
|
|
for (sect = 0; sect < bc->num_sects; sect++)
|
|
|
if (bc->sects[sect].type == JS_BCST_SYMTAB)
|
|
|
{
|
|
|
JSSymtabEntry *se;
|
|
|
char buf[257];
|
|
|
|
|
|
cp = bc->sects[sect].data;
|
|
|
|
|
|
/* The number of symbols. */
|
|
|
JS_BC_READ_INT32 (cp, num_symtab_entries);
|
|
|
|
|
|
symtab = js_calloc (vm, num_symtab_entries + 1, sizeof (*symtab));
|
|
|
|
|
|
/* Make the terminator by hand. */
|
|
|
symtab[num_symtab_entries].offset = code_len;
|
|
|
|
|
|
/* Enter symbols. */
|
|
|
se = symtab;
|
|
|
for (ui = 4; ui < bc->sects[sect].length; se++)
|
|
|
{
|
|
|
for (i = 0; cp[ui]; ui++, i++)
|
|
|
buf[i] = cp[ui];
|
|
|
buf[i] = '\0';
|
|
|
|
|
|
se->name = js_strdup (vm, buf);
|
|
|
ui++;
|
|
|
|
|
|
JS_BC_READ_INT32 (cp + ui, se->offset);
|
|
|
ui += 4;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
/* Check if we have debugging information. */
|
|
|
debug_info = NULL;
|
|
|
debug_info_len = 0;
|
|
|
for (sect = 0; sect < bc->num_sects; sect++)
|
|
|
if (bc->sects[sect].type == JS_BCST_DEBUG)
|
|
|
{
|
|
|
debug_info = bc->sects[sect].data;
|
|
|
debug_info_len = bc->sects[sect].length;
|
|
|
}
|
|
|
|
|
|
/* Clear error message and old exec result. */
|
|
|
vm->error[0] = '\0';
|
|
|
vm->exec_result.type = JS_UNDEFINED;
|
|
|
|
|
|
PROFILING_ON ();
|
|
|
|
|
|
/* Execute. */
|
|
|
result = (*vm->dispatch_execute) (vm, bc, symtab, num_symtab_entries,
|
|
|
consts_offset,
|
|
|
anonymous_function_offset,
|
|
|
debug_info, debug_info_len,
|
|
|
NULL, NULL, 0, NULL);
|
|
|
}
|
|
|
|
|
|
PROFILING_OFF ();
|
|
|
|
|
|
if (symtab)
|
|
|
{
|
|
|
for (ui = 0; ui < num_symtab_entries; ui++)
|
|
|
js_free (symtab[ui].name);
|
|
|
js_free (symtab);
|
|
|
}
|
|
|
|
|
|
/* Pop all error handler frames from the handler chain. */
|
|
|
for (; vm->error_handler != saved_handler; vm->error_handler = handler)
|
|
|
{
|
|
|
handler = vm->error_handler->next;
|
|
|
js_free (vm->error_handler);
|
|
|
}
|
|
|
|
|
|
/* Restore virtual machine's idea about the stack top. */
|
|
|
vm->sp = saved_sp;
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
|
|
|
int
|
|
|
js_vm_apply (JSVirtualMachine *vm, char *func_name, JSNode *func,
|
|
|
unsigned int argc, JSNode *argv)
|
|
|
{
|
|
|
int result = 1;
|
|
|
JSNode *saved_sp;
|
|
|
JSErrorHandlerFrame *handler, *saved_handler;
|
|
|
|
|
|
/* Initialize error handler. */
|
|
|
|
|
|
saved_sp = vm->sp;
|
|
|
saved_handler = vm->error_handler;
|
|
|
|
|
|
handler = js_calloc (NULL, 1, sizeof (*handler));
|
|
|
if (handler == NULL)
|
|
|
{
|
|
|
sprintf (vm->error, "VM: out of memory");
|
|
|
return 0;
|
|
|
}
|
|
|
handler->next = vm->error_handler;
|
|
|
vm->error_handler = handler;
|
|
|
|
|
|
if (setjmp (vm->error_handler->error_jmp))
|
|
|
{
|
|
|
/* An error occurred. */
|
|
|
result = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* Clear error message and old exec result. */
|
|
|
vm->error[0] = '\0';
|
|
|
vm->exec_result.type = JS_UNDEFINED;
|
|
|
|
|
|
if (func_name)
|
|
|
/* Lookup the function. */
|
|
|
func = &vm->globals[js_vm_intern (vm, func_name)];
|
|
|
|
|
|
/* Check what kind of function should be called. */
|
|
|
if (func->type == JS_FUNC)
|
|
|
{
|
|
|
PROFILING_ON ();
|
|
|
|
|
|
/* Call function. */
|
|
|
result = (*vm->dispatch_execute) (vm, NULL, NULL, 0, 0, 0,
|
|
|
NULL, 0,
|
|
|
NULL, func, argc, argv);
|
|
|
}
|
|
|
else if (func->type == JS_BUILTIN
|
|
|
&& func->u.vbuiltin->info->global_method_proc != NULL)
|
|
|
{
|
|
|
(*func->u.vbuiltin->info->global_method_proc) (
|
|
|
vm,
|
|
|
func->u.vbuiltin->info,
|
|
|
func->u.vbuiltin->instance_context,
|
|
|
&vm->exec_result,
|
|
|
argv);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if (func_name)
|
|
|
sprintf (vm->error, "undefined function `%s' in apply",
|
|
|
func_name);
|
|
|
else
|
|
|
sprintf (vm->error, "undefiend function in apply");
|
|
|
|
|
|
result = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
PROFILING_OFF ();
|
|
|
|
|
|
/* Pop all error handler frames from the handler chain. */
|
|
|
for (; vm->error_handler != saved_handler; vm->error_handler = handler)
|
|
|
{
|
|
|
handler = vm->error_handler->next;
|
|
|
js_free (vm->error_handler);
|
|
|
}
|
|
|
|
|
|
/* Restore virtual machine's idea about the stack top. */
|
|
|
vm->sp = saved_sp;
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
|
|
|
int
|
|
|
js_vm_call_method (JSVirtualMachine *vm, JSNode *object,
|
|
|
const char *method_name, unsigned int argc, JSNode *argv)
|
|
|
{
|
|
|
int result = 1;
|
|
|
JSNode *saved_sp;
|
|
|
JSErrorHandlerFrame *handler, *saved_handler;
|
|
|
JSSymbol symbol;
|
|
|
|
|
|
/* Initialize error handler. */
|
|
|
|
|
|
saved_sp = vm->sp;
|
|
|
saved_handler = vm->error_handler;
|
|
|
|
|
|
handler = js_calloc (NULL, 1, sizeof (*handler));
|
|
|
if (handler == NULL)
|
|
|
{
|
|
|
sprintf (vm->error, "VM: out of memory");
|
|
|
return 0;
|
|
|
}
|
|
|
handler->next = vm->error_handler;
|
|
|
vm->error_handler = handler;
|
|
|
|
|
|
if (setjmp (vm->error_handler->error_jmp))
|
|
|
{
|
|
|
/* An error occurred. */
|
|
|
result = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* Intern the method name. */
|
|
|
symbol = js_vm_intern (vm, method_name);
|
|
|
|
|
|
/* Clear error message and old exec result. */
|
|
|
vm->error[0] = '\0';
|
|
|
vm->exec_result.type = JS_UNDEFINED;
|
|
|
|
|
|
/* What kind of object was called? */
|
|
|
|
|
|
if (object->type == JS_BUILTIN)
|
|
|
{
|
|
|
if (object->u.vbuiltin->info->method_proc)
|
|
|
{
|
|
|
if ((*object->u.vbuiltin->info->method_proc) (
|
|
|
vm,
|
|
|
object->u.vbuiltin->info,
|
|
|
object->u.vbuiltin->instance_context,
|
|
|
symbol,
|
|
|
&vm->exec_result, argv)
|
|
|
== JS_PROPERTY_UNKNOWN)
|
|
|
{
|
|
|
sprintf (vm->error, "call_method: unknown method");
|
|
|
result = 0;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
sprintf (vm->error, "illegal builtin object for call_method");
|
|
|
result = 0;
|
|
|
}
|
|
|
}
|
|
|
else if (object->type == JS_OBJECT)
|
|
|
{
|
|
|
JSNode method;
|
|
|
|
|
|
/* Fetch the method's implementation. */
|
|
|
if (js_vm_object_load_property (vm, object->u.vobject, symbol,
|
|
|
&method)
|
|
|
== JS_PROPERTY_FOUND)
|
|
|
{
|
|
|
/* The property has been defined in the object. */
|
|
|
if (method.type != JS_FUNC)
|
|
|
{
|
|
|
sprintf (vm->error, "call_method: unknown method");
|
|
|
result = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
PROFILING_ON ();
|
|
|
result = (*vm->dispatch_execute) (vm, NULL, NULL, 0, 0, 0,
|
|
|
NULL, 0,
|
|
|
object, &method, argc,
|
|
|
argv);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
/* Let the built-in Object handle this. */
|
|
|
goto to_builtin_please;
|
|
|
}
|
|
|
else if (vm->prim[object->type])
|
|
|
{
|
|
|
/* The primitive language types. */
|
|
|
to_builtin_please:
|
|
|
if ((*vm->prim[object->type]->method_proc) (vm,
|
|
|
vm->prim[object->type],
|
|
|
object, symbol,
|
|
|
&vm->exec_result,
|
|
|
argv)
|
|
|
== JS_PROPERTY_UNKNOWN)
|
|
|
{
|
|
|
sprintf (vm->error, "call_method: unknown method");
|
|
|
result = 0;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
sprintf (vm->error, "illegal object for call_method");
|
|
|
result = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
PROFILING_OFF ();
|
|
|
|
|
|
/* Pop all error handler frames from the handler chain. */
|
|
|
for (; vm->error_handler != saved_handler; vm->error_handler = handler)
|
|
|
{
|
|
|
handler = vm->error_handler->next;
|
|
|
js_free (vm->error_handler);
|
|
|
}
|
|
|
|
|
|
/* Restore virtual machine's idea about the stack top. */
|
|
|
vm->sp = saved_sp;
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
|
|
|
const char *
|
|
|
js_vm_func_name (JSVirtualMachine *vm, void *pc)
|
|
|
{
|
|
|
return (*vm->dispatch_func_name) (vm, pc);
|
|
|
}
|
|
|
|
|
|
|
|
|
const char *
|
|
|
js_vm_debug_position (JSVirtualMachine *vm, unsigned int *linenum_return)
|
|
|
{
|
|
|
return (*vm->dispatch_debug_position) (vm, linenum_return);
|
|
|
}
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
js_vm_intern_with_len (JSVirtualMachine *vm, const char *name,
|
|
|
unsigned int len)
|
|
|
{
|
|
|
JSHashBucket *b;
|
|
|
unsigned int pos = js_count_hash (name, len) % JS_HASH_TABLE_SIZE;
|
|
|
|
|
|
for (b = vm->globals_hash[pos]; b; b = b->next)
|
|
|
if (strcmp (b->name, name) == 0)
|
|
|
return b->u.ui;
|
|
|
|
|
|
b = js_malloc (vm, sizeof (*b));
|
|
|
b->name = js_strdup (vm, name);
|
|
|
|
|
|
b->next = vm->globals_hash[pos];
|
|
|
vm->globals_hash[pos] = b;
|
|
|
|
|
|
/* Alloc space from the globals array. */
|
|
|
if (vm->num_globals >= vm->globals_alloc)
|
|
|
{
|
|
|
vm->globals = js_realloc (vm, vm->globals,
|
|
|
(vm->globals_alloc + 1024) * sizeof (JSNode));
|
|
|
vm->globals_alloc += 1024;
|
|
|
}
|
|
|
|
|
|
/* Initialize symbol's name spaces. */
|
|
|
vm->globals[vm->num_globals].type = JS_UNDEFINED;
|
|
|
b->u.ui = vm->num_globals++;
|
|
|
|
|
|
return b->u.ui;
|
|
|
}
|
|
|
|
|
|
|
|
|
const char *
|
|
|
js_vm_symname (JSVirtualMachine *vm, JSSymbol sym)
|
|
|
{
|
|
|
int i;
|
|
|
JSHashBucket *b;
|
|
|
|
|
|
for (i = 0; i < JS_HASH_TABLE_SIZE; i++)
|
|
|
for (b = vm->globals_hash[i]; b; b = b->next)
|
|
|
if (b->u.ui == sym)
|
|
|
return b->name;
|
|
|
|
|
|
return "???";
|
|
|
}
|
|
|
|
|
|
|
|
|
void
|
|
|
js_vm_error (JSVirtualMachine *vm)
|
|
|
{
|
|
|
const char *file;
|
|
|
unsigned int ln;
|
|
|
char error[1024];
|
|
|
|
|
|
file = js_vm_debug_position (vm, &ln);
|
|
|
if (file)
|
|
|
{
|
|
|
sprintf (error, "%s:%u: %s", file, ln, vm->error);
|
|
|
strcpy (vm->error, error);
|
|
|
}
|
|
|
|
|
|
if (vm->stacktrace_on_error)
|
|
|
{
|
|
|
sprintf (error, "VM: error: %s%s", vm->error,
|
|
|
JS_HOST_LINE_BREAK);
|
|
|
js_iostream_write (vm->s_stderr, error, strlen (error));
|
|
|
|
|
|
js_vm_stacktrace (vm, (unsigned int) -1);
|
|
|
}
|
|
|
|
|
|
if (vm->error_handler->sp)
|
|
|
/*
|
|
|
* We are jumping to a catch-block. Format our error message to
|
|
|
* the `thrown' node.
|
|
|
*/
|
|
|
js_vm_make_string (vm, &vm->error_handler->thrown,
|
|
|
vm->error, strlen (vm->error));
|
|
|
|
|
|
longjmp (vm->error_handler->error_jmp, 1);
|
|
|
|
|
|
/* NOTREACHED (I hope). */
|
|
|
|
|
|
sprintf (error, "VM: no valid error handler initialized%s",
|
|
|
JS_HOST_LINE_BREAK);
|
|
|
js_iostream_write (vm->s_stderr, error, strlen (error));
|
|
|
js_iostream_flush (vm->s_stderr);
|
|
|
|
|
|
abort ();
|
|
|
}
|
|
|
|
|
|
/* Delete proc for garbaged built-in objects. */
|
|
|
static void
|
|
|
destroy_builtin (void *ptr)
|
|
|
{
|
|
|
JSBuiltin *bi = ptr;
|
|
|
|
|
|
if (bi->info->delete_proc)
|
|
|
(*bi->info->delete_proc) (bi->info, bi->instance_context);
|
|
|
}
|
|
|
|
|
|
/* Delete proc for garbaged built-in info. */
|
|
|
static void
|
|
|
destroy_builtin_info (void *ptr)
|
|
|
{
|
|
|
JSBuiltinInfo *i = ptr;
|
|
|
|
|
|
if (i->obj_context_delete)
|
|
|
(*i->obj_context_delete) (i->obj_context);
|
|
|
}
|
|
|
|
|
|
|
|
|
JSBuiltinInfo *
|
|
|
js_vm_builtin_info_create (JSVirtualMachine *vm)
|
|
|
{
|
|
|
JSNode prototype;
|
|
|
JSBuiltinInfo *i = js_vm_alloc_destroyable (vm, sizeof (*i));
|
|
|
|
|
|
i->destroy = destroy_builtin_info;
|
|
|
i->prototype = js_vm_object_new (vm);
|
|
|
|
|
|
/*
|
|
|
* Set the __proto__ property to null. We have no prototype object
|
|
|
* above us.
|
|
|
*/
|
|
|
prototype.type = JS_NULL;
|
|
|
js_vm_object_store_property (vm, i->prototype, vm->syms.s___proto__,
|
|
|
&prototype);
|
|
|
|
|
|
return i;
|
|
|
}
|
|
|
|
|
|
|
|
|
void
|
|
|
js_vm_builtin_create (JSVirtualMachine *vm, JSNode *result,
|
|
|
JSBuiltinInfo *info, void *instance_context)
|
|
|
{
|
|
|
result->type = JS_BUILTIN;
|
|
|
result->u.vbuiltin = js_vm_alloc_destroyable (vm, sizeof (JSBuiltin));
|
|
|
result->u.vbuiltin->destroy = destroy_builtin;
|
|
|
result->u.vbuiltin->info = info;
|
|
|
|
|
|
if (instance_context)
|
|
|
{
|
|
|
JSNode prototype;
|
|
|
|
|
|
result->u.vbuiltin->instance_context = instance_context;
|
|
|
result->u.vbuiltin->prototype = js_vm_object_new (vm);
|
|
|
|
|
|
/* Set the __proto__ chain. */
|
|
|
|
|
|
prototype.type = JS_OBJECT;
|
|
|
prototype.u.vobject = info->prototype;
|
|
|
|
|
|
js_vm_object_store_property (vm, result->u.vbuiltin->prototype,
|
|
|
vm->syms.s___proto__, &prototype);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/*
|
|
|
* Static functions.
|
|
|
*/
|
|
|
|
|
|
extern void js_builtin_core (JSVirtualMachine *vm);
|
|
|
|
|
|
extern void js_builtin_Array (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_Boolean (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_Function (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_Number (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_Object (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_String (JSVirtualMachine *vm);
|
|
|
|
|
|
extern void js_builtin_Date (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_Directory (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_File (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_Math (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_RegExp (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_System (JSVirtualMachine *vm);
|
|
|
extern void js_builtin_VM (JSVirtualMachine *vm);
|
|
|
|
|
|
|
|
|
static void
|
|
|
intern_builtins (JSVirtualMachine *vm)
|
|
|
{
|
|
|
/*
|
|
|
* The initialization order is significant. The RegExp object must be
|
|
|
* initialized before String.
|
|
|
*/
|
|
|
|
|
|
/* The core global methods. */
|
|
|
js_builtin_core (vm);
|
|
|
|
|
|
/* Our builtin extensions. */
|
|
|
js_builtin_Date (vm);
|
|
|
js_builtin_Directory (vm);
|
|
|
js_builtin_File (vm);
|
|
|
js_builtin_Math (vm);
|
|
|
js_builtin_RegExp (vm);
|
|
|
js_builtin_System (vm);
|
|
|
js_builtin_VM (vm);
|
|
|
|
|
|
/* Language objects. */
|
|
|
js_builtin_Array (vm);
|
|
|
js_builtin_Boolean (vm);
|
|
|
js_builtin_Function (vm);
|
|
|
js_builtin_Number (vm);
|
|
|
js_builtin_Object (vm);
|
|
|
js_builtin_String (vm);
|
|
|
}
|
|
|
#endif /*CL_EXPERIMENTAL*/
|
|
|
|