mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1016 lines
23 KiB
1016 lines
23 KiB
/*
|
|
* Examples for low memory techniques
|
|
*/
|
|
|
|
#if defined(DUK_CMDLINE_LOWMEM)
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include "duktape.h"
|
|
#include "duk_cmdline.h"
|
|
#include "duk_alloc_pool.h"
|
|
|
|
#if defined(DUK_USE_ROM_OBJECTS) && defined(DUK_USE_HEAPPTR16)
|
|
/* Pointer compression with ROM strings/objects:
|
|
*
|
|
* For now, use DUK_USE_ROM_OBJECTS to signal the need for compressed ROM
|
|
* pointers. DUK_USE_ROM_PTRCOMP_FIRST is provided for the ROM pointer
|
|
* compression range minimum to avoid duplication in user code.
|
|
*/
|
|
#if 0 /* This extern declaration is provided by duktape.h, array provided by duktape.c. */
|
|
extern const void * const duk_rom_compressed_pointers[];
|
|
#endif
|
|
static const void *duk__romptr_low = NULL;
|
|
static const void *duk__romptr_high = NULL;
|
|
#define DUK__ROMPTR_COMPRESSION
|
|
#define DUK__ROMPTR_FIRST ((duk_uint_t) DUK_USE_ROM_PTRCOMP_FIRST)
|
|
#endif
|
|
|
|
#define LOWMEM_NUM_POOLS 28
|
|
|
|
#define LOWMEM_HEAP_SIZE (255 * 1024)
|
|
|
|
static const duk_pool_config lowmem_config[LOWMEM_NUM_POOLS] = {
|
|
{ 8, 10 * 8, 0 },
|
|
{ 12, 600 * 12, 0 },
|
|
{ 16, 300 * 16, 0 },
|
|
{ 20, 300 * 20, 0 },
|
|
{ 24, 300 * 24, 0 },
|
|
{ 28, 250 * 28, 0 },
|
|
{ 32, 150 * 32, 0 },
|
|
{ 40, 150 * 40, 0 },
|
|
{ 48, 50 * 48, 0 },
|
|
{ 52, 50 * 52, 0 },
|
|
{ 56, 50 * 56, 0 },
|
|
{ 60, 50 * 60, 0 },
|
|
{ 64, 50 * 64, 0 },
|
|
{ 96, 50 * 96, 0 },
|
|
{ 196, 0, 196 }, /* duk_heap, with heap ptr compression, ROM strings+objects */
|
|
{ 232, 0, 232 }, /* duk_hthread, with heap ptr compression, ROM strings+objects */
|
|
{ 256, 16 * 256, 0 },
|
|
{ 288, 1 * 288, 0 },
|
|
{ 320, 1 * 320, 0 },
|
|
{ 400, 0, 400 }, /* duk_hthread, with heap ptr compression, RAM strings+objects */
|
|
{ 520, 0, 520 }, /* duk_heap, with heap ptr compression, RAM strings+objects */
|
|
{ 512, 16 * 512, 0 },
|
|
{ 768, 0, 768 }, /* initial value stack for packed duk_tval */
|
|
{ 1024, 6 * 1024, 0 },
|
|
{ 2048, 5 * 2048, 0 },
|
|
{ 4096, 3 * 4096, 0 },
|
|
{ 8192, 3 * 8192, 0 },
|
|
{ 16384, 1 * 16384, 0 },
|
|
};
|
|
|
|
static duk_pool_state lowmem_state[LOWMEM_NUM_POOLS];
|
|
|
|
static duk_pool_global lowmem_global;
|
|
|
|
void *lowmem_pool_ptr = NULL;
|
|
|
|
uint8_t *lowmem_ram = NULL;
|
|
|
|
static void *duk__lose_const(const void *ptr) {
|
|
/* Somewhat portable way of losing a const without warnings.
|
|
* Another approach is to cast through intptr_t, but that
|
|
* type is not always available.
|
|
*/
|
|
union {
|
|
const void *p;
|
|
void *q;
|
|
} u;
|
|
u.p = ptr;
|
|
return u.q;
|
|
}
|
|
|
|
static void duk__safe_print_chars(const char *p, duk_size_t len, int until_nul) {
|
|
duk_size_t i;
|
|
|
|
fprintf(stderr, "\"");
|
|
for (i = 0; i < len; i++) {
|
|
unsigned char x = (unsigned char) p[i];
|
|
if (until_nul && x == 0U) {
|
|
break;
|
|
}
|
|
if (x < 0x20 || x >= 0x7e || x == '"' || x == '\'' || x == '\\') {
|
|
fprintf(stderr, "\\x%02x", (int) x);
|
|
} else {
|
|
fprintf(stderr, "%c", (char) x);
|
|
}
|
|
}
|
|
fprintf(stderr, "\"");
|
|
}
|
|
|
|
|
|
void lowmem_init(void) {
|
|
void *ptr;
|
|
|
|
lowmem_ram = (uint8_t *) malloc(LOWMEM_HEAP_SIZE);
|
|
if (lowmem_ram == NULL) {
|
|
fprintf(stderr, "Failed to allocate lowmem heap\n");
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
ptr = duk_alloc_pool_init((char *) lowmem_ram,
|
|
LOWMEM_HEAP_SIZE,
|
|
lowmem_config,
|
|
lowmem_state,
|
|
LOWMEM_NUM_POOLS,
|
|
&lowmem_global);
|
|
if (ptr == NULL) {
|
|
free(lowmem_ram);
|
|
lowmem_ram = NULL;
|
|
fprintf(stderr, "Failed to init lowmem pool\n");
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
lowmem_pool_ptr = ptr;
|
|
|
|
#if defined(DUK__ROMPTR_COMPRESSION)
|
|
/* Scan ROM pointer range for faster detection of "is 'p' a ROM pointer"
|
|
* later on.
|
|
*/
|
|
if (1) {
|
|
const void * const * ptrs = (const void * const *) duk_rom_compressed_pointers;
|
|
duk__romptr_low = duk__romptr_high = (const void *) *ptrs;
|
|
while (*ptrs) {
|
|
if (*ptrs > duk__romptr_high) {
|
|
duk__romptr_high = (const void *) *ptrs;
|
|
}
|
|
if (*ptrs < duk__romptr_low) {
|
|
duk__romptr_low = (const void *) *ptrs;
|
|
}
|
|
ptrs++;
|
|
}
|
|
fprintf(stderr, "romptrs: low=%p high=%p\n",
|
|
(const void *) duk__romptr_low, (const void *) duk__romptr_high);
|
|
fflush(stderr);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void lowmem_free(void) {
|
|
if (lowmem_ram != NULL) {
|
|
free(lowmem_ram);
|
|
lowmem_ram = NULL;
|
|
}
|
|
lowmem_pool_ptr = NULL;
|
|
}
|
|
|
|
static duk_ret_t lowmem__dump_binding(duk_context *ctx) {
|
|
lowmem_dump();
|
|
return 0;
|
|
}
|
|
|
|
void lowmem_dump(void) {
|
|
int i;
|
|
duk_pool_global_stats global_stats;
|
|
|
|
for (i = 0; i < LOWMEM_NUM_POOLS; i++) {
|
|
duk_pool_state *s = &lowmem_state[i];
|
|
duk_pool_stats stats;
|
|
|
|
duk_alloc_pool_get_pool_stats(s, &stats);
|
|
|
|
fprintf(stderr, " %2ld: %4ld %5ldB | free %4ld %5ldB | used %4ld %5ldB | waste %5ldB | hwm %4ld (%3ld%%)%s\n",
|
|
(long) i, (long) s->count, (long) s->size,
|
|
(long) stats.free_count, (long) stats.free_bytes,
|
|
(long) stats.used_count, (long) stats.used_bytes,
|
|
(long) stats.waste_bytes, (long) stats.hwm_used_count,
|
|
(long) ((double) stats.hwm_used_count / (double) s->count * 100.0),
|
|
(stats.hwm_used_count == s->count ? " !" : ""));
|
|
}
|
|
|
|
/* This causes another walk over the individual pools which is a bit
|
|
* inelegant, but we want the highwater mark stats too.
|
|
*/
|
|
duk_alloc_pool_get_global_stats(&lowmem_global, &global_stats);
|
|
|
|
fprintf(stderr, " TOTAL: %ld bytes used, %ld bytes waste, %ld bytes free, %ld bytes total; highwater %ld used, %ld waste\n",
|
|
(long) global_stats.used_bytes, (long) global_stats.waste_bytes,
|
|
(long) global_stats.free_bytes, (long) (global_stats.used_bytes + global_stats.free_bytes),
|
|
(long) global_stats.hwm_used_bytes, (long) global_stats.hwm_waste_bytes);
|
|
fflush(stderr);
|
|
}
|
|
|
|
void lowmem_register(duk_context *ctx) {
|
|
duk_push_global_object(ctx);
|
|
duk_push_string(ctx, "dumpHeap");
|
|
duk_push_c_function(ctx, lowmem__dump_binding, 0);
|
|
duk_def_prop(ctx, -3, DUK_DEFPROP_SET_WRITABLE |
|
|
DUK_DEFPROP_CLEAR_ENUMERABLE |
|
|
DUK_DEFPROP_SET_CONFIGURABLE |
|
|
DUK_DEFPROP_HAVE_VALUE);
|
|
duk_pop(ctx);
|
|
}
|
|
|
|
/*
|
|
* Wrapped alloc functions
|
|
*
|
|
* Used to write an alloc log.
|
|
*/
|
|
|
|
static FILE *lowmem_alloc_log = NULL;
|
|
|
|
static void lowmem_write_alloc_log(const char *fmt, ...) {
|
|
va_list ap;
|
|
char buf[256];
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
buf[sizeof(buf) - 1] = (char) 0;
|
|
va_end(ap);
|
|
|
|
if (lowmem_alloc_log == NULL) {
|
|
lowmem_alloc_log = fopen("/tmp/lowmem-alloc-log.txt", "wb");
|
|
if (lowmem_alloc_log == NULL) {
|
|
fprintf(stderr, "WARNING: failed to write alloc log, ignoring\n");
|
|
fflush(stderr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
(void) fwrite((const void *) buf, 1, strlen(buf), lowmem_alloc_log);
|
|
(void) fflush(lowmem_alloc_log);
|
|
}
|
|
|
|
void *lowmem_alloc_wrapped(void *udata, duk_size_t size) {
|
|
void *ret = duk_alloc_pool(udata, size);
|
|
if (size > 0 && ret == NULL) {
|
|
lowmem_write_alloc_log("A FAIL %ld\n", (long) size);
|
|
} else if (ret == NULL) {
|
|
lowmem_write_alloc_log("A NULL %ld\n", (long) size);
|
|
} else {
|
|
lowmem_write_alloc_log("A %p %ld\n", ret, (long) size);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void *lowmem_realloc_wrapped(void *udata, void *ptr, duk_size_t size) {
|
|
void *ret = duk_realloc_pool(udata, ptr, size);
|
|
if (size > 0 && ret == NULL) {
|
|
if (ptr == NULL) {
|
|
lowmem_write_alloc_log("R NULL -1 FAIL %ld\n", (long) size);
|
|
} else {
|
|
lowmem_write_alloc_log("R %p -1 FAIL %ld\n", ptr, (long) size);
|
|
}
|
|
} else if (ret == NULL) {
|
|
if (ptr == NULL) {
|
|
lowmem_write_alloc_log("R NULL -1 NULL %ld\n", (long) size);
|
|
} else {
|
|
lowmem_write_alloc_log("R %p -1 NULL %ld\n", ptr, (long) size);
|
|
}
|
|
} else {
|
|
if (ptr == NULL) {
|
|
lowmem_write_alloc_log("R NULL -1 %p %ld\n", ret, (long) size);
|
|
} else {
|
|
lowmem_write_alloc_log("R %p -1 %p %ld\n", ptr, ret, (long) size);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void lowmem_free_wrapped(void *udata, void *ptr) {
|
|
duk_free_pool(udata, ptr);
|
|
if (ptr == NULL) {
|
|
/* Ignore. */
|
|
} else {
|
|
lowmem_write_alloc_log("F %p -1\n", ptr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Example pointer compression functions.
|
|
*
|
|
* 'base' is chosen so that no non-NULL pointer results in a zero result
|
|
* which is reserved for NULL pointers.
|
|
*/
|
|
|
|
duk_uint16_t lowmem_enc16(void *ud, void *p) {
|
|
duk_uint32_t ret;
|
|
char *base = (char *) lowmem_ram - 4;
|
|
|
|
#if defined(DUK__ROMPTR_COMPRESSION)
|
|
if (p >= duk__romptr_low && p <= duk__romptr_high) {
|
|
/* The if-condition should be the fastest possible check
|
|
* for "is 'p' in ROM?". If pointer is in ROM, we'd like
|
|
* to compress it quickly. Here we just scan a ~1K array
|
|
* which is very bad for performance and for illustration
|
|
* only.
|
|
*/
|
|
const void * const * ptrs = duk_rom_compressed_pointers;
|
|
while (*ptrs) {
|
|
if (*ptrs == p) {
|
|
ret = (duk_uint32_t) DUK__ROMPTR_FIRST + (duk_uint32_t) (ptrs - duk_rom_compressed_pointers);
|
|
#if 0
|
|
fprintf(stderr, "lowmem_enc16: rom pointer: %p -> 0x%04lx\n", (void *) p, (long) ret);
|
|
fflush(stderr);
|
|
#endif
|
|
return (duk_uint16_t) ret;
|
|
}
|
|
ptrs++;
|
|
}
|
|
|
|
/* We should really never be here: Duktape should only be
|
|
* compressing pointers which are in the ROM compressed
|
|
* pointers list, which are known when configuring sources.
|
|
* We go on, causing a pointer compression error.
|
|
*/
|
|
fprintf(stderr, "lowmem_enc16: rom pointer: %p could not be compressed, should never happen\n", (void *) p);
|
|
fflush(stderr);
|
|
}
|
|
#endif
|
|
|
|
/* Userdata is not needed in this case but would be useful if heap
|
|
* pointer compression were used for multiple heaps. The userdata
|
|
* allows the callback to distinguish between heaps and their base
|
|
* pointers.
|
|
*
|
|
* If not needed, the userdata can be left out during compilation
|
|
* by simply ignoring the userdata argument of the pointer encode
|
|
* and decode macros. It is kept here so that any bugs in actually
|
|
* providing the value inside Duktape are revealed during compilation.
|
|
*/
|
|
(void) ud;
|
|
#if 1
|
|
/* Ensure that we always get the heap_udata given in heap creation.
|
|
* (Useful for Duktape development, not needed for user programs.)
|
|
*/
|
|
if (ud != (void *) lowmem_pool_ptr) {
|
|
fprintf(stderr, "invalid udata for lowmem_enc16: %p\n", ud);
|
|
fflush(stderr);
|
|
}
|
|
#endif
|
|
|
|
if (p == NULL) {
|
|
ret = 0;
|
|
} else {
|
|
ret = (duk_uint32_t) (((char *) p - base) >> 2);
|
|
}
|
|
#if 0
|
|
fprintf(stderr, "lowmem_enc16: %p -> %u\n", p, (unsigned int) ret);
|
|
#endif
|
|
if (ret > 0xffffUL) {
|
|
fprintf(stderr, "Failed to compress pointer: %p (ret was %ld)\n", (void *) p, (long) ret);
|
|
fflush(stderr);
|
|
abort();
|
|
}
|
|
#if defined(DUK__ROMPTR_COMPRESSION)
|
|
if (ret >= (duk_uint32_t) DUK__ROMPTR_FIRST) {
|
|
fprintf(stderr, "Failed to compress pointer, in 16-bit range but matches romptr range: %p (ret was %ld)\n", (void *) p, (long) ret);
|
|
fflush(stderr);
|
|
abort();
|
|
}
|
|
#endif
|
|
return (duk_uint16_t) ret;
|
|
}
|
|
|
|
void *lowmem_dec16(void *ud, duk_uint16_t x) {
|
|
void *ret;
|
|
char *base = (char *) lowmem_ram - 4;
|
|
|
|
#if defined(DUK__ROMPTR_COMPRESSION)
|
|
if (x >= (duk_uint16_t) DUK__ROMPTR_FIRST) {
|
|
/* This is a blind lookup, could check index validity.
|
|
* Duktape should never decompress a pointer which would
|
|
* be out-of-bounds here.
|
|
*/
|
|
ret = (void *) duk__lose_const(duk_rom_compressed_pointers[x - (duk_uint16_t) DUK__ROMPTR_FIRST]);
|
|
#if 0
|
|
fprintf(stderr, "lowmem_dec16: rom pointer: 0x%04lx -> %p\n", (long) x, ret);
|
|
fflush(stderr);
|
|
#endif
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* See userdata discussion in lowmem_enc16(). */
|
|
(void) ud;
|
|
#if 1
|
|
/* Ensure that we always get the heap_udata given in heap creation. */
|
|
if (ud != (void *) lowmem_pool_ptr) {
|
|
fprintf(stderr, "invalid udata for lowmem_dec16: %p\n", ud);
|
|
fflush(stderr);
|
|
}
|
|
#endif
|
|
|
|
if (x == 0) {
|
|
ret = NULL;
|
|
} else {
|
|
ret = (void *) (base + (((duk_uint32_t) x) << 2));
|
|
}
|
|
#if 0
|
|
fprintf(stderr, "lowmem_dec16: %u -> %p\n", (unsigned int) x, ret);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Simplified example of an external strings strategy where incoming strings
|
|
* are written sequentially into a fixed, memory mapped flash area.
|
|
*
|
|
* The example first scans if the string is already in the flash (which may
|
|
* happen if the same string is interned multiple times), then adds it to
|
|
* flash if there is space.
|
|
*
|
|
* This example is too slow to be used in a real world application: there
|
|
* should be e.g. a hash table to quickly check for strings that are already
|
|
* present in the string data (similarly to how string interning works in
|
|
* Duktape itself).
|
|
*/
|
|
|
|
static uint8_t lowmem_strdata[65536];
|
|
static size_t lowmem_strdata_used = 0;
|
|
|
|
const void *lowmem_extstr_check_1(const void *ptr, duk_size_t len) {
|
|
uint8_t *p, *p_end;
|
|
uint8_t initial;
|
|
uint8_t *ret;
|
|
size_t left;
|
|
|
|
(void) duk__safe_print_chars; /* potentially unused */
|
|
|
|
if (len <= 3) {
|
|
/* It's not worth it to make very small strings external, as
|
|
* they would take the same space anyway. Also avoids zero
|
|
* length degenerate case.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Check if we already have the string. Be careful to compare for
|
|
* NUL terminator too, it is NOT present in 'ptr'. This algorithm
|
|
* is too simplistic and way too slow for actual use.
|
|
*/
|
|
|
|
initial = ((const uint8_t *) ptr)[0];
|
|
for (p = lowmem_strdata, p_end = p + lowmem_strdata_used; p != p_end; p++) {
|
|
if (*p != initial) {
|
|
continue;
|
|
}
|
|
left = (size_t) (p_end - p);
|
|
if (left >= len + 1 &&
|
|
memcmp(p, ptr, len) == 0 &&
|
|
p[len] == 0) {
|
|
ret = p;
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_check_1: ptr=%p, len=%ld ",
|
|
(void *) ptr, (long) len);
|
|
duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/);
|
|
fprintf(stderr, " -> existing %p (used=%ld)\n",
|
|
(void *) ret, (long) lowmem_strdata_used);
|
|
#endif
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Not present yet, check if we have space. Again, be careful to
|
|
* ensure there is space for a NUL following the input data.
|
|
*/
|
|
|
|
if (lowmem_strdata_used + len + 1 > sizeof(lowmem_strdata)) {
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_check_1: ptr=%p, len=%ld ", (void *) ptr, (long) len);
|
|
duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/);
|
|
fprintf(stderr, " -> no space (used=%ld)\n", (long) lowmem_strdata_used);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* There is space, add the string to our collection, being careful
|
|
* to append the NUL.
|
|
*/
|
|
|
|
ret = lowmem_strdata + lowmem_strdata_used;
|
|
memcpy(ret, ptr, len);
|
|
ret[len] = (uint8_t) 0;
|
|
lowmem_strdata_used += len + 1;
|
|
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_check_1: ptr=%p, len=%ld -> ", (void *) ptr, (long) len);
|
|
duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/);
|
|
fprintf(stderr, " -> %p (used=%ld)\n", (void *) ret, (long) lowmem_strdata_used);
|
|
#endif
|
|
return (const void *) ret;
|
|
}
|
|
|
|
void lowmem_extstr_free_1(const void *ptr) {
|
|
(void) ptr;
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_free_1: freeing extstr %p -> ", ptr);
|
|
duk__safe_print_chars((const char *) ptr, DUK_SIZE_MAX, 1 /*until_nul*/);
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Simplified example of an external strings strategy where a set of strings
|
|
* is gathered during application compile time and baked into the application
|
|
* binary.
|
|
*
|
|
* Duktape built-in strings are available from duk_source_meta.json in a
|
|
* prepared source directory, see tools/duk_meta_to_strarray.py. There
|
|
* may also be a lot of application specific strings, e.g. those used by
|
|
* application specific APIs. These must be gathered through some other
|
|
* means, see e.g. tools/scan_strings.py.
|
|
*/
|
|
|
|
static const char *strdata_duk_builtin_strings[] = {
|
|
/*
|
|
* These strings are from tools/duk_meta_to_strarray.py
|
|
*/
|
|
|
|
"Logger",
|
|
"Thread",
|
|
"Pointer",
|
|
"Buffer",
|
|
"DecEnv",
|
|
"ObjEnv",
|
|
"",
|
|
"global",
|
|
"Arguments",
|
|
"JSON",
|
|
"Math",
|
|
"Error",
|
|
"RegExp",
|
|
"Date",
|
|
"Number",
|
|
"Boolean",
|
|
"String",
|
|
"Array",
|
|
"Function",
|
|
"Object",
|
|
"Null",
|
|
"Undefined",
|
|
"{_func:true}",
|
|
"{\x22" "_func\x22" ":true}",
|
|
"{\x22" "_ninf\x22" ":true}",
|
|
"{\x22" "_inf\x22" ":true}",
|
|
"{\x22" "_nan\x22" ":true}",
|
|
"{\x22" "_undef\x22" ":true}",
|
|
"toLogString",
|
|
"clog",
|
|
"l",
|
|
"n",
|
|
"fatal",
|
|
"error",
|
|
"warn",
|
|
"debug",
|
|
"trace",
|
|
"raw",
|
|
"fmt",
|
|
"current",
|
|
"resume",
|
|
"compact",
|
|
"jc",
|
|
"jx",
|
|
"base64",
|
|
"hex",
|
|
"dec",
|
|
"enc",
|
|
"fin",
|
|
"gc",
|
|
"act",
|
|
"info",
|
|
"version",
|
|
"env",
|
|
"modLoaded",
|
|
"modSearch",
|
|
"errThrow",
|
|
"errCreate",
|
|
"compile",
|
|
"\x82" "Regbase",
|
|
"\x82" "Thread",
|
|
"\x82" "Handler",
|
|
"\x82" "Finalizer",
|
|
"\x82" "Callee",
|
|
"\x82" "Map",
|
|
"\x82" "Args",
|
|
"\x82" "This",
|
|
"\x82" "Pc2line",
|
|
"\x82" "Source",
|
|
"\x82" "Varenv",
|
|
"\x82" "Lexenv",
|
|
"\x82" "Varmap",
|
|
"\x82" "Formals",
|
|
"\x82" "Bytecode",
|
|
"\x82" "Next",
|
|
"\x82" "Target",
|
|
"\x82" "Value",
|
|
"pointer",
|
|
"buffer",
|
|
"\x82" "Tracedata",
|
|
"lineNumber",
|
|
"fileName",
|
|
"pc",
|
|
"stack",
|
|
"ThrowTypeError",
|
|
"Duktape",
|
|
"id",
|
|
"require",
|
|
"__proto__",
|
|
"setPrototypeOf",
|
|
"ownKeys",
|
|
"enumerate",
|
|
"deleteProperty",
|
|
"has",
|
|
"Proxy",
|
|
"callee",
|
|
"Invalid Date",
|
|
"[...]",
|
|
"\x0a" "\x09",
|
|
" ",
|
|
",",
|
|
"-0",
|
|
"+0",
|
|
"0",
|
|
"-Infinity",
|
|
"+Infinity",
|
|
"Infinity",
|
|
"object",
|
|
"string",
|
|
"number",
|
|
"boolean",
|
|
"undefined",
|
|
"stringify",
|
|
"tan",
|
|
"sqrt",
|
|
"sin",
|
|
"round",
|
|
"random",
|
|
"pow",
|
|
"min",
|
|
"max",
|
|
"log",
|
|
"floor",
|
|
"exp",
|
|
"cos",
|
|
"ceil",
|
|
"atan2",
|
|
"atan",
|
|
"asin",
|
|
"acos",
|
|
"abs",
|
|
"SQRT2",
|
|
"SQRT1_2",
|
|
"PI",
|
|
"LOG10E",
|
|
"LOG2E",
|
|
"LN2",
|
|
"LN10",
|
|
"E",
|
|
"message",
|
|
"name",
|
|
"input",
|
|
"index",
|
|
"(?:)",
|
|
"lastIndex",
|
|
"multiline",
|
|
"ignoreCase",
|
|
"source",
|
|
"test",
|
|
"exec",
|
|
"toGMTString",
|
|
"setYear",
|
|
"getYear",
|
|
"toJSON",
|
|
"toISOString",
|
|
"toUTCString",
|
|
"setUTCFullYear",
|
|
"setFullYear",
|
|
"setUTCMonth",
|
|
"setMonth",
|
|
"setUTCDate",
|
|
"setDate",
|
|
"setUTCHours",
|
|
"setHours",
|
|
"setUTCMinutes",
|
|
"setMinutes",
|
|
"setUTCSeconds",
|
|
"setSeconds",
|
|
"setUTCMilliseconds",
|
|
"setMilliseconds",
|
|
"setTime",
|
|
"getTimezoneOffset",
|
|
"getUTCMilliseconds",
|
|
"getMilliseconds",
|
|
"getUTCSeconds",
|
|
"getSeconds",
|
|
"getUTCMinutes",
|
|
"getMinutes",
|
|
"getUTCHours",
|
|
"getHours",
|
|
"getUTCDay",
|
|
"getDay",
|
|
"getUTCDate",
|
|
"getDate",
|
|
"getUTCMonth",
|
|
"getMonth",
|
|
"getUTCFullYear",
|
|
"getFullYear",
|
|
"getTime",
|
|
"toLocaleTimeString",
|
|
"toLocaleDateString",
|
|
"toTimeString",
|
|
"toDateString",
|
|
"now",
|
|
"UTC",
|
|
"parse",
|
|
"toPrecision",
|
|
"toExponential",
|
|
"toFixed",
|
|
"POSITIVE_INFINITY",
|
|
"NEGATIVE_INFINITY",
|
|
"NaN",
|
|
"MIN_VALUE",
|
|
"MAX_VALUE",
|
|
"substr",
|
|
"trim",
|
|
"toLocaleUpperCase",
|
|
"toUpperCase",
|
|
"toLocaleLowerCase",
|
|
"toLowerCase",
|
|
"substring",
|
|
"split",
|
|
"search",
|
|
"replace",
|
|
"match",
|
|
"localeCompare",
|
|
"charCodeAt",
|
|
"charAt",
|
|
"fromCharCode",
|
|
"reduceRight",
|
|
"reduce",
|
|
"filter",
|
|
"map",
|
|
"forEach",
|
|
"some",
|
|
"every",
|
|
"lastIndexOf",
|
|
"indexOf",
|
|
"unshift",
|
|
"splice",
|
|
"sort",
|
|
"slice",
|
|
"shift",
|
|
"reverse",
|
|
"push",
|
|
"pop",
|
|
"join",
|
|
"concat",
|
|
"isArray",
|
|
"arguments",
|
|
"caller",
|
|
"bind",
|
|
"call",
|
|
"apply",
|
|
"propertyIsEnumerable",
|
|
"isPrototypeOf",
|
|
"hasOwnProperty",
|
|
"valueOf",
|
|
"toLocaleString",
|
|
"toString",
|
|
"constructor",
|
|
"set",
|
|
"get",
|
|
"enumerable",
|
|
"configurable",
|
|
"writable",
|
|
"value",
|
|
"keys",
|
|
"isExtensible",
|
|
"isFrozen",
|
|
"isSealed",
|
|
"preventExtensions",
|
|
"freeze",
|
|
"seal",
|
|
"defineProperties",
|
|
"defineProperty",
|
|
"create",
|
|
"getOwnPropertyNames",
|
|
"getOwnPropertyDescriptor",
|
|
"getPrototypeOf",
|
|
"prototype",
|
|
"length",
|
|
"alert",
|
|
"print",
|
|
"unescape",
|
|
"escape",
|
|
"encodeURIComponent",
|
|
"encodeURI",
|
|
"decodeURIComponent",
|
|
"decodeURI",
|
|
"isFinite",
|
|
"isNaN",
|
|
"parseFloat",
|
|
"parseInt",
|
|
"eval",
|
|
"URIError",
|
|
"TypeError",
|
|
"SyntaxError",
|
|
"ReferenceError",
|
|
"RangeError",
|
|
"EvalError",
|
|
"break",
|
|
"case",
|
|
"catch",
|
|
"continue",
|
|
"debugger",
|
|
"default",
|
|
"delete",
|
|
"do",
|
|
"else",
|
|
"finally",
|
|
"for",
|
|
"function",
|
|
"if",
|
|
"in",
|
|
"instanceof",
|
|
"new",
|
|
"return",
|
|
"switch",
|
|
"this",
|
|
"throw",
|
|
"try",
|
|
"typeof",
|
|
"var",
|
|
"void",
|
|
"while",
|
|
"with",
|
|
"class",
|
|
"const",
|
|
"enum",
|
|
"export",
|
|
"extends",
|
|
"import",
|
|
"super",
|
|
"null",
|
|
"true",
|
|
"false",
|
|
"implements",
|
|
"interface",
|
|
"let",
|
|
"package",
|
|
"private",
|
|
"protected",
|
|
"public",
|
|
"static",
|
|
"yield",
|
|
|
|
/*
|
|
* These strings are manually added, and would be gathered in some
|
|
* application specific manner.
|
|
*/
|
|
|
|
"foo",
|
|
"bar",
|
|
"quux",
|
|
"enableFrob",
|
|
"disableFrob"
|
|
/* ... */
|
|
};
|
|
|
|
const void *lowmem_extstr_check_2(const void *ptr, duk_size_t len) {
|
|
int i, n;
|
|
|
|
(void) duk__safe_print_chars; /* potentially unused */
|
|
|
|
/* Linear scan. An actual implementation would need some acceleration
|
|
* structure, e.g. select a sublist based on first character.
|
|
*
|
|
* NOTE: input string (behind 'ptr' with 'len' bytes) DOES NOT have a
|
|
* trailing NUL character. Any strings returned from this function
|
|
* MUST have a trailing NUL character.
|
|
*/
|
|
|
|
n = (int) (sizeof(strdata_duk_builtin_strings) / sizeof(const char *));
|
|
for (i = 0; i < n; i++) {
|
|
const char *str;
|
|
|
|
str = strdata_duk_builtin_strings[i];
|
|
if (strlen(str) == len && memcmp(ptr, (const void *) str, len) == 0) {
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_check_2: ptr=%p, len=%ld ",
|
|
(void *) ptr, (long) len);
|
|
duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/);
|
|
fprintf(stderr, " -> constant string index %ld\n", (long) i);
|
|
#endif
|
|
return (void *) duk__lose_const(strdata_duk_builtin_strings[i]);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_check_2: ptr=%p, len=%ld ",
|
|
(void *) ptr, (long) len);
|
|
duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/);
|
|
fprintf(stderr, " -> not found\n");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
void lowmem_extstr_free_2(const void *ptr) {
|
|
(void) ptr;
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_free_2: freeing extstr %p -> ", ptr);
|
|
duk__safe_print_chars((const char *) ptr, DUK_SIZE_MAX, 1 /*until_nul*/);
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* External strings strategy intended for valgrind testing: external strings
|
|
* are allocated using malloc()/free() so that valgrind can be used to ensure
|
|
* that strings are e.g. freed exactly once.
|
|
*/
|
|
|
|
const void *lowmem_extstr_check_3(const void *ptr, duk_size_t len) {
|
|
duk_uint8_t *ret;
|
|
|
|
(void) duk__safe_print_chars; /* potentially unused */
|
|
|
|
ret = malloc((size_t) len + 1);
|
|
if (ret == NULL) {
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_check_3: ptr=%p, len=%ld ",
|
|
(void *) ptr, (long) len);
|
|
duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/);
|
|
fprintf(stderr, " -> malloc failed, return NULL\n");
|
|
#endif
|
|
return (const void *) NULL;
|
|
}
|
|
|
|
if (len > 0) {
|
|
memcpy((void *) ret, ptr, (size_t) len);
|
|
}
|
|
ret[len] = (duk_uint8_t) 0;
|
|
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_check_3: ptr=%p, len=%ld ",
|
|
(void *) ptr, (long) len);
|
|
duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/);
|
|
fprintf(stderr, " -> %p\n", (void *) ret);
|
|
#endif
|
|
return (const void *) ret;
|
|
}
|
|
|
|
void lowmem_extstr_free_3(const void *ptr) {
|
|
(void) ptr;
|
|
#if 0
|
|
fprintf(stderr, "lowmem_extstr_free_3: freeing extstr %p -> ", ptr);
|
|
duk__safe_print_chars((const char *) ptr, DUK_SIZE_MAX, 1 /*until_nul*/);
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
free((void *) duk__lose_const(ptr));
|
|
}
|
|
|
|
/*
|
|
* Execution timeout example
|
|
*/
|
|
|
|
#define AJSHEAP_EXEC_TIMEOUT 5 /* seconds */
|
|
|
|
static time_t curr_pcall_start = 0;
|
|
static long exec_timeout_check_counter = 0;
|
|
|
|
void lowmem_start_exec_timeout(void) {
|
|
curr_pcall_start = time(NULL);
|
|
}
|
|
|
|
void lowmem_clear_exec_timeout(void) {
|
|
curr_pcall_start = 0;
|
|
}
|
|
|
|
duk_bool_t lowmem_exec_timeout_check(void *udata) {
|
|
time_t now = time(NULL);
|
|
time_t diff = now - curr_pcall_start;
|
|
|
|
(void) udata; /* not needed */
|
|
|
|
exec_timeout_check_counter++;
|
|
#if 0
|
|
fprintf(stderr, "exec timeout check %ld: now=%ld, start=%ld, diff=%ld\n",
|
|
(long) exec_timeout_check_counter, (long) now, (long) curr_pcall_start, (long) diff);
|
|
fflush(stderr);
|
|
#endif
|
|
|
|
if (curr_pcall_start == 0) {
|
|
/* protected call not yet running */
|
|
return 0;
|
|
}
|
|
if (diff > AJSHEAP_EXEC_TIMEOUT) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#else /* DUK_CMDLINE_LOWMEM */
|
|
|
|
int duk_lowmem_dummy = 0; /* to avoid empty source file */
|
|
|
|
#endif /* DUK_CMDLINE_LOWMEM */
|
|
|