diff --git a/lib/lpc/identifier.c b/lib/lpc/identifier.c index 0c02a9a..fddb893 100644 --- a/lib/lpc/identifier.c +++ b/lib/lpc/identifier.c @@ -9,9 +9,9 @@ ident_hash_elem_list_t *ihe_list = NULL; static int num_free = 0; -static ident_hash_elem_t **ident_hash_table; -static ident_hash_elem_t **ident_hash_head; -static ident_hash_elem_t **ident_hash_tail; +static ident_hash_elem_t **ident_hash_table; /* ident hash, current position */ +static ident_hash_elem_t **ident_hash_head; /* ident hash, head of permanent idents */ +static ident_hash_elem_t **ident_hash_tail; /* ident hash, tail of permanent idents */ static ident_hash_elem_t *ident_dirty_list = 0; @@ -31,38 +31,49 @@ static ident_hash_elem_t *ident_dirty_list = 0; * hash value are used often within close proximity in the source. * This should be rare, esp since the hash table is fairly sparse. * - * ident_hash_table[hash] points to our current position (last lookup) - * ident_hash_head[hash] points to the first permanent identifier - * ident_hash_tail[hash] points to the last one + * ident_hash_table[hash] points to our current position (last successful lookup) in the bucket + * ident_hash_head[hash] points to the first permanent identifier in the bucket + * ident_hash_tail[hash] points to the last permanent identifier in the bucket * ident_dirty_list is a linked list of identifiers that need to be cleaned * when we're done; this happens if you define a global or function with - * the same name (hashed) as an efun or sefun. + * the same name (hashed) as an efun or simul efun. */ #define CHECK_ELEM(x, y, z) if (!strcmp((x)->name, (y))) { \ if (((x)->token & IHE_RESWORD) || ((x)->sem_value)) { z } \ else return 0; } +/** + * Lookup an identifier by name in the identifier hash table. + * Each bucket in the hash table is a circular linked list. + * In the case of collisions, the bucket is searched starting from current position (last lookup). + * When found, the found element is rotated to the front of the list for faster future access. + * + * @param name The name of the identifier. + * @return A pointer to the identifier hash element, or NULL if not found. + */ ident_hash_elem_t *lookup_ident (const char *name) { int h = IdentHash (name); ident_hash_elem_t *hptr, *hptr2; - if (ident_hash_table && (hptr = ident_hash_table[h])) + if (ident_hash_table && (hptr = ident_hash_table[h])) /* non-empty bucket */ { - CHECK_ELEM (hptr, name, return hptr;); + CHECK_ELEM (hptr, name, return hptr;); /* if found, already at front */ hptr2 = hptr->next; while (hptr2 != hptr) { - CHECK_ELEM (hptr2, name, ident_hash_table[h] = hptr2; - return hptr2;); + CHECK_ELEM (hptr2, name, ident_hash_table[h] = hptr2; return hptr2;); /* if found, rotate to here */ hptr2 = hptr2->next; } } - return 0; + return 0; /* not found */ } /** - * @brief Find or add a permanent identifier. + * Find or add a permanent identifier. The following identifiers are added as permanent: + * - efuns + * - simul efuns + * (reserved words are also permanent, but they are added by add_keyword() in lex.c) * @param name The name of the identifier. No reference is made to the string after this call. * @return A pointer to the identifier hash element. */ @@ -92,8 +103,8 @@ ident_hash_elem_t* find_or_add_perm_ident (char *name) { { /* no collision, add to hash table */ hptr = (ident_hash_table[h] = ALLOCATE (ident_hash_elem_t, TAG_PERM_IDENT, "find_or_add_perm_ident:2")); - ident_hash_head[h] = hptr; - ident_hash_tail[h] = hptr; + ident_hash_head[h] = hptr; /* first permanent ident */ + ident_hash_tail[h] = hptr; /* last permanent ident */ hptr->next = hptr; } @@ -118,12 +129,16 @@ typedef struct lname_linked_buf_s { static lname_linked_buf_t *lnamebuf = 0; static size_t lb_index = 4096; -static char *alloc_local_name (const char *name) -{ - size_t len = strlen (name) + 1; +/** + * Allocate a local name string. + * @param name The name to allocate. + * @return A pointer to the allocated name string. + */ +static char *alloc_local_name (const char *name) { + size_t len = strlen (name) + 1; /* include null terminator */ char *res; - if (lb_index + len > 4096) + if (lb_index + len > sizeof (lnamebuf->block)) { lname_linked_buf_t *new_buf; new_buf = ALLOCATE (lname_linked_buf_t, TAG_COMPILER, "alloc_local_name"); @@ -137,9 +152,10 @@ static char *alloc_local_name (const char *name) return res; } -void -free_unused_identifiers () -{ +/** + * Free unused identifiers from the identifier hash table. + */ +void free_unused_identifiers () { ident_hash_elem_list_t *ihel, *next; lname_linked_buf_t *lnb, *lnbn; int i; @@ -165,10 +181,12 @@ free_unused_identifiers () ident_dirty_list = ident_dirty_list->next_dirty; } + /* remove non-permanent identifier hash elements from hash table */ for (i = 0; i < IDENT_HASH_SIZE; i++) if ((ident_hash_table[i] = ident_hash_head[i])) ident_hash_tail[i]->next = ident_hash_head[i]; + /* free all non-permanent identifier hash elements */ ihel = ihe_list; while (ihel) { @@ -179,6 +197,7 @@ free_unused_identifiers () ihe_list = 0; num_free = 0; + /* free all allocated local name buffers */ lnb = lnamebuf; while (lnb) { @@ -190,13 +209,14 @@ free_unused_identifiers () lb_index = 4096; } -static ident_hash_elem_t * -quick_alloc_ident_entry () -{ +/** + * Quickly allocate an identifier hash element. + * @return A pointer to the allocated identifier hash element. + */ +static ident_hash_elem_t* quick_alloc_ident_entry () { if (num_free) { num_free--; - return &(ihe_list->items[num_free]); } else { @@ -204,9 +224,9 @@ quick_alloc_ident_entry () ihel = ALLOCATE (ident_hash_elem_list_t, TAG_COMPILER, "quick_alloc_ident_entry"); ihel->next = ihe_list; ihe_list = ihel; - num_free = 127; - return &(ihe_list->items[127]); + num_free = sizeof (ihe_list->items) / sizeof (ihe_list->items[0]) - 1; } + return &(ihe_list->items[num_free]); } ident_hash_elem_t *find_or_add_ident (char *name, int flags) { @@ -283,9 +303,16 @@ ident_hash_elem_t *find_or_add_ident (char *name, int flags) { } /** - * @brief Add a keyword to the identifier hash table. + * Add a keyword to the identifier hash table. + * + * A keyword is a permanent identifier that is always defined (can be found by lookup_ident + * regardless of sem_value). + * + * @param name The name of the keyword. + * @param entry Pointer to the keyword entry. This must point to a mutable memory location + * that remains valid until deinit_identifiers() is called. */ -void add_keyword_t (char *name, keyword_t * entry) { +void add_keyword (const char *name, keyword_t* entry) { int h = IdentHash (name); if (ident_hash_table[h]) @@ -325,7 +352,9 @@ void init_identifiers () { } /** - * @brief Deinitialize identifier management structures. All identifiers including permanents are freed. + * Deinitialize identifier management structures. + * All identifiers including permanents are freed. + * (reserved words are not freed since they point to static memory locations) */ void deinit_identifiers () { int i, n = 0; @@ -346,7 +375,7 @@ void deinit_identifiers () { else { if (!(hptr->token & IHE_RESWORD)) - debug_warn ("leaked identifier: %s", hptr->name); + debug_warn ("leaked identifier (token: 0x%x): %s", hptr->token, hptr->name); hptr = hptr->next; } } diff --git a/lib/lpc/identifier.h b/lib/lpc/identifier.h index ad58a44..261df5a 100644 --- a/lib/lpc/identifier.h +++ b/lib/lpc/identifier.h @@ -9,15 +9,16 @@ #define INDENT_HASH_SIZE 1024 /* must be a power of 2 */ -typedef struct { +/* identifier semantics */ +typedef struct defined_name_s { short local_num, global_num, efun_num; short function_num, simul_num, class_num; } defined_name_t; typedef struct ident_hash_elem_s { char *name; - short token; /* only flags */ - short sem_value; /* for these, a count of the ambiguity */ + short token; /* only flags */ + short sem_value; /* 0: reserved word or not defined, >1 a count of the ambiguity */ struct ident_hash_elem_s *next; /* the fields above must correspond to struct keyword_t */ struct ident_hash_elem_s *next_dirty; @@ -31,7 +32,7 @@ typedef struct ident_hash_elem_list_s { extern ident_hash_elem_list_t *ihe_list; -typedef struct { +typedef struct keyword_s { char *word; unsigned short token; /* flags here too */ short sem_value; /* semantic value for predefined tokens */ @@ -56,6 +57,6 @@ ident_hash_elem_t *find_or_add_ident(char *, int); ident_hash_elem_t *find_or_add_perm_ident(char *); ident_hash_elem_t *lookup_ident(const char *); void free_unused_identifiers(void); -void add_keyword_t (char *name, keyword_t * entry); +void add_keyword (const char *name, keyword_t * entry); void init_identifiers(void); void deinit_identifiers(void); diff --git a/lib/lpc/lex.c b/lib/lpc/lex.c index dee1c40..97c4bbd 100644 --- a/lib/lpc/lex.c +++ b/lib/lpc/lex.c @@ -3510,7 +3510,7 @@ void init_keywords (void) { for (i = 0; i < sizeof(reswords) / sizeof(reswords[0]); i++) { keyword_t *entry = &reswords[i]; - add_keyword_t (entry->word, entry); + add_keyword (entry->word, entry); } } diff --git a/lib/port/debug.h b/lib/port/debug.h index 0018b8b..353b6f4 100644 --- a/lib/port/debug.h +++ b/lib/port/debug.h @@ -31,7 +31,7 @@ #define TT_COMPILE 0020U #define TT_SIMUL_EFUN 0040U #define TT_BACKEND 0100U -#define TT_COMM 0200U +#define TT_COMM 0200U #define TT_MEMORY 0400U /** diff --git a/src/simul_efun.c b/src/simul_efun.c index 5fa9ba9..33edbe7 100644 --- a/src/simul_efun.c +++ b/src/simul_efun.c @@ -16,6 +16,8 @@ #include "lpc/object.h" #include "lpc/include/origin.h" +#include + /* * This file rewritten by Beek because it was inefficient and slow. We * now keep track of two mappings: @@ -78,7 +80,8 @@ void init_simul_efun (const char *file) { } /** - * @brief Remove all old simul_efuns from the tables and identifier hash. + * Remove all current simul efuns from the simuls/simuls_sorted tables and + * turn off IHE_SIMUL flag of the simul efun name identifier hash. */ static void remove_simuls () { int i; @@ -98,15 +101,16 @@ static void remove_simuls () { ihe->sem_value--; ihe->dn.simul_num = -1; ihe->token &= ~IHE_SIMUL; - free_string (simuls_sorted[i].name); /* reference added by find_or_add_simul_efun() */ + /* the simul efun could be overriding an efun, do not remove the permanent identifier here */ } + free_string (simuls_sorted[i].name); /* reference added by find_or_add_simul_efun() */ } } /** - * Add all functions in 'prog' as simul_efuns. - * If 'prog' is NULL, remove all simul_efuns. - * @param prog The new program containing the simul_efuns to add. + * Add all functions in 'prog' as simul efuns. + * If 'prog' is NULL, remove all simul efuns. + * @param prog The new program containing the simul efuns to add. */ static void get_simul_efuns (program_t* prog) { @@ -118,7 +122,7 @@ static void get_simul_efuns (program_t* prog) { remove_simuls (); if (!num_new) { - opt_trace (TT_SIMUL_EFUN|2, "no new simul_efuns, removing all"); + opt_trace (TT_SIMUL_EFUN|2, "no new simul efuns, removing all"); FREE (simuls_sorted); simuls_sorted = 0; FREE (simuls); @@ -161,7 +165,6 @@ static void get_simul_efuns (program_t* prog) { index = func_entry->inh.index; func_entry = FIND_FUNC_ENTRY (nprog, index); } - find_or_add_simul_efun (nprog, func_entry->def.f_index, i); } @@ -271,21 +274,19 @@ static void find_or_add_simul_efun (program_t* prog, function_number_t index, fu void set_simul_efun (object_t* ob) { - if (!ob || ob->flags & O_DESTRUCTED) + if (ob && ob->flags & O_DESTRUCTED) error ("Bad simul_efun object\n"); - get_simul_efuns (ob->prog); - simul_efun_ob = ob; - add_ref (simul_efun_ob, "set_simul_efun"); -} - -void unset_simul_efun () { + if (simul_efun_ob) + { + get_simul_efuns (NULL); /* remove all simul_efuns */ + free_object (simul_efun_ob, "set_simul_efun"); + } - get_simul_efuns (NULL); /* remove all simul_efuns */ - if (simul_efun_ob) { - free_object (simul_efun_ob, "unset_simul_efun"); - simul_efun_ob = NULL; - } + if (!(simul_efun_ob = ob)) + return; + get_simul_efuns (simul_efun_ob->prog); + add_ref (simul_efun_ob, "set_simul_efun"); } void call_simul_efun (int simul_num, int num_args) diff --git a/src/simul_efun.h b/src/simul_efun.h index e066506..d2bbb71 100644 --- a/src/simul_efun.h +++ b/src/simul_efun.h @@ -35,12 +35,6 @@ extern void init_simul_efun(const char *file); */ extern void set_simul_efun (object_t *ob); -/** - * Unset the current simul_efun object. - * @return Upon return, the simul_efun object is unset and its reference is released. - */ -extern void unset_simul_efun(); - /** * Find the index (simul_num) of a simul_efun function by its name string pointer. * The search uses binary search on the address of the name string (not string comparison). @@ -57,8 +51,14 @@ extern void unset_simul_efun(); extern int find_simul_efun(const char *func_name); /** - * Call a simul_efun by its index in the simul_efun table. + * Call a simul_efun by its index (simul_num) in the simul_efun table. + * * The LPC opcode F_SIMUL_EFUN uses this function. + * Note that the simul_num is stored in the compiled opcode, which is bounded to the simul efun name at compile time. + * If the simul_efun object changes, the opcode still uses the same simul_num to refer to the simul efun. + * If the new simul_efun object does not have a function of that name or changed the order of function definitions, + * the behavior is undefined. + * * @param simul_num The index in the simul_efun table. It is stored in LPC opcode as a 16-bit unsigned integer. * @param num_args The number of arguments on the stack. * @return Upon return, the return value is on the stack. diff --git a/src/simulate.c b/src/simulate.c index 1dcb13a..17c9074 100644 --- a/src/simulate.c +++ b/src/simulate.c @@ -28,6 +28,7 @@ #include "efuns/sprintf.h" #include "port/ansi.h" +#include #include /* @@ -261,10 +262,21 @@ void set_master (object_t * ob) { svalue_t *ret; char *uid = NULL; - master_ob = ob; + if (ob && ob->flags & O_DESTRUCTED) + error ("Bad master object\n"); + + if (master_ob) + { + /* release reference to the old master_ob */ + assert (master_ob->ref > 1); + free_object (master_ob, "set_master"); + } + if (!(master_ob = ob)) + return; /* Make sure master_ob is never made a dangling pointer. */ add_ref (master_ob, "set_master"); + opt_trace (TT_EVAL|1, "master object ref = %d", master_ob->ref); ret = apply_master_ob (APPLY_GET_ROOT_UID, 0); if (ret && (ret->type == T_STRING)) @@ -938,7 +950,19 @@ void destruct_object (object_t * ob) { error ("*Only this_object() can be destructed from move_or_destruct."); if ((ob == simul_efun_ob) && master_ob) - error ("*Cannot destruct simul_efun_object while master_object exists."); + { + /* simul efun object is a special object that the F_SIMUL_EFUN instruction in compiled LPC + * opcodes relies on the correct associations of simul_num and the function defined in + * simul_efun_ob. If both master_object and simul_efun_object exist, then + * destructing simul_efun_object would break this association and possibly corrupt the + * program of master object. + * + * In Neolith, we allow the simul_efun_object to be destructed only when the + * master_object does not exist, such as during mudlib reloading. More validations are + * checked in the set_simul_efun() function when replacing the simul_efun_ob with a new object. + */ + error ("*Cannot destruct simul_efun_object while master_object exists."); + } /* * check if object has an efun socket referencing it for a callback. if @@ -1058,8 +1082,10 @@ void destruct_object (object_t * ob) { vital_obj_name = CONFIG_STR (__MASTER_FILE__); else if (ob == simul_efun_ob) vital_obj_name = CONFIG_STR (__SIMUL_EFUN_FILE__); + if (vital_obj_name) { + /* reload vital object */ char new_name[PATH_MAX]; if (!strip_name (vital_obj_name, new_name, sizeof (new_name))) { @@ -1069,36 +1095,28 @@ void destruct_object (object_t * ob) { } opt_trace (TT_EVAL|1, "reloading vital object: /%s", tmp); new_ob = load_object (tmp, 0); + if (!new_ob) + { + ob->name = tmp; + sp--; + error ("*Destruct on vital object failed: new copy failed to reload."); + } } - /* handle these two carefully, since they are rather vital */ - if (vital_obj_name && !new_ob) - { - ob->name = tmp; - sp--; - error ("*Destruct on vital object failed: new copy failed to reload."); - } - - free_object (ob, "vital object reference"); if (ob == master_ob) { - if (new_ob) - set_master(new_ob); - else - master_ob = NULL; + set_master (new_ob); } else if (ob == simul_efun_ob) { - simul_efun_ob = NULL; /* prevents recursively calling destruct_object() on simul_efun_ob*/ - unset_simul_efun (); - if (new_ob) - set_simul_efun (new_ob); + set_simul_efun (new_ob); } + sp--; /* error handler */ + /* Set the name back so we can remove it from the hash table. Also be careful not to remove the new object, which has the same name. */ - sp--; /* error handler */ ob->name = tmp; if (new_ob) { @@ -1110,7 +1128,7 @@ void destruct_object (object_t * ob) { new_ob->name = tmp; } else - remove_object_hash (ob); + remove_object_hash (ob); /* not vital object */ opt_trace (TT_EVAL|1, "removed /%s from object name hash table", ob->name); /* @@ -1141,7 +1159,6 @@ void destruct_object (object_t * ob) { ob->contains = 0; ob->next_all = obj_list_destruct; obj_list_destruct = ob; - opt_trace (TT_EVAL|1, "added /%s to destruct list", ob->name); set_heart_beat (ob, 0); ob->flags |= O_DESTRUCTED; /* mark as destructed */ @@ -1149,7 +1166,7 @@ void destruct_object (object_t * ob) { /* moved this here from destruct2() -- see comments in destruct2() */ if (ob->interactive) { - opt_trace (TT_EVAL|1, "disconnecting /%s as interactive object", ob->name); + opt_trace (TT_COMM|1, "disconnecting /%s as interactive object", ob->name); remove_interactive (ob, 1); } opt_trace (TT_EVAL|1, "finished destructing: /%s", ob->name); @@ -2816,7 +2833,6 @@ void tear_down_simulate() { CLEAR_CONFIG_STR(__MASTER_FILE__); /* do not reload master_ob */ current_object = master_ob; destruct_object (master_ob); - master_ob = NULL; } /* simul_efun_ob, if loaded, must be the LAST object to be destructed in the simulated virtual world * because the LPC compiler uses index of simul_efuns in the generated opcode. The program_t of @@ -2824,10 +2840,8 @@ void tear_down_simulate() { */ if (simul_efun_ob) { CLEAR_CONFIG_STR(__SIMUL_EFUN_FILE__); /* do not reload simul_efun_ob */ - object_t* old_simul_efun_ob = simul_efun_ob; - unset_simul_efun(); - current_object = old_simul_efun_ob; - destruct_object (old_simul_efun_ob); + current_object = simul_efun_ob; + destruct_object (simul_efun_ob); } remove_destructed_objects(); // actually free destructed objects clear_apply_cache(); // clear shared strings referenced by apply cache