/**
* \file boolexp.c
*
* \brief Boolean expression parser.
*
* This code implements a parser for boolean expressions of the form
* used in locks. Summary of parsing rules, lowest to highest precedence:
* \verbatim
* E -> T; E -> T | E (or)
* T -> F; T -> F & T (and)
* F -> !F;F -> A (not)
* A -> @L; A -> I (indirect)
* I -> =Identifier ; I -> C (equality)
* C -> +Identifier ; C -> O (carry)
* O -> $Identifier ; O -> L (owner)
* L -> (E); L -> eval/attr/flag lock (parens, special atoms)
* L -> E, L is an object name or dbref or #t* or #f* (simple atoms)
* \endverbatim
*
* Previously, the boolexp code just used a parse tree of the
* boolexp. Now, it turns the parse tree into bytecode that can be
* stored in the chunk manager. It probably also evaluates faster, but
* no profiling has been done to support this claim. It certainly
* involves less non-tail recursion.
*
* It's a three-stage process. First, the lock string is turned into a
* parse tree. Second, the tree is walked and "assembler" instructions
* are generated, including labels for jumps. Third, the "assembly" is
* stepped through and bytecode emitted, with labeled jumps replaced
* by distances that are offsets from the start of the
* bytecode. Pretty standard stuff.
*
* Each bytecode instruction is 5 bytes long (1 byte opcode + 4 byte
* int argument), and the minimum number of instructions in a compiled
* boolexp is 2, for a minimum size of 10 bytes. Compare this to the
* size of one parse-tree node, 16 bytes. Savings appear to be
* substantial, especially with complex locks with lots of ors or
* ands.
*
* Many lock keys have string arguments. The strings are standard
* 0-terminated C strings stored in a section of the same string as
* the bytecode instructions, starting right after the last
* instruction. They're accessed by offset from the start of the
* bytecode string. If the same string appears multiple times in the
* lock, only one copy is actually present in the string section.
*
* The VM for the bytecode is a simple register-based one. The
* registers are R, the result register, set by test instructions and
* a few others, and S, the string register, which holds the extra
* string in the few tests that need two (A:B, A/B). There are
* instructions for each lock key type. There's a few extra ones to
* make decompiling back into a string dead easy. Nothing very
* complex.
*
* Future directions?
* \verbatim
* [Development] Raevnos is tempted in passing to re-write the boolexp parser in
* lex and yacc.
* [Development] Brazil laughs.
* [Development] Brazil says, "That might not be a bad idea."
* [Development] Raevnos has redone everything else in boolexp.c in Penn, so why
* not? :)
* [Development] Raevnos says, "Using the justification that it's a lot easier to
* expand the langage by adding new key types that way."
* \endverbatim
*
* So now you know who to blame if that particular item appears in a
* changelog for Penn or MUX.
*
* On a more serious note, a) #1234 is equivalent to b)
* =#1234|+#1234. Detecting b and turning it into a, or vis versa,
* would be easy to do. a is a common key, but turning it into b gets
* rid of a test instruction in the VM, at the cost of more
* instructions in generated lock bytecode. CISC or RISC? :) It's also
* easy to turn !!foo into foo, but nobody makes locks like that. Same
* with !#true and !#false. Of possibly more interest is rearranging
* the logic when ands, ors and nots are being used together. For
* example, !a|!b can become !(a&b).
*
* There's more useful room for improvement in the lock
* @warnings. Checking things like flag and power keys for valid flags
* comes to mind.
*
*/
#include "copyrite.h"
#include "config.h"
#include <ctype.h>
#include <string.h>
#include "conf.h"
#include "mushdb.h"
#include "match.h"
#include "externs.h"
#include "lock.h"
#include "parse.h"
#include "attrib.h"
#include "flags.h"
#include "dbdefs.h"
#include "log.h"
#include "extchat.h"
#include "strtree.h"
#include "confmagic.h"
#ifdef WIN32
#pragma warning( disable : 4761) /* disable warning re conversion */
#endif
/* #define DEBUG_BYTECODE */
/** Parse tree node types */
typedef enum boolexp_type {
BOOLEXP_AND, /**< A&B */
BOOLEXP_OR, /**< A|B */
BOOLEXP_NOT, /**< !A */
BOOLEXP_CONST, /**< A */
BOOLEXP_ATR, /**< A:B */
BOOLEXP_IND, /**< @A/B */
BOOLEXP_CARRY, /**< +A */
BOOLEXP_IS, /**< =A */
BOOLEXP_OWNER, /**< $A */
BOOLEXP_EVAL, /**< A/B */
BOOLEXP_FLAG, /**< A^B */
BOOLEXP_BOOL /**< #true, #false */
} boolexp_type;
/** An attribute lock specification for the parse tree.
* This structure is a piece of a boolexp that's used to store
* attribute locks (CANDO:1), eval locks (CANDO/1), and flag locks
* FLAG^WIZARD.
*/
struct boolatr {
const char *name; /**< Name of attribute, flag, etc. to test */
char text[BUFFER_LEN]; /**< Value to test against */
};
/** A boolean expression parse tree node.
* Boolean expressions are most widely used in locks. This structure
* is a general representation of the possible boolean expressions
* that can be specified in MUSHcode. It's used internally by the lock
* compiler.
*/
struct boolexp_node {
/** Type of expression.
* The type of expressio is one of the boolexp_type's, such as
* and, or, not, constant, attribute, indirect, carry, is,
* owner, eval, flag, etc.
*/
boolexp_type type;
dbref thing; /**< An object, or a boolean val */
/** The expression itself.
* This union comprises the various possible types of data we
* might need to represent any of the expression types.
*/
union {
/** And and or locks: combinations of boolexps.
* This union member is used with and and or locks.
*/
struct {
struct boolexp_node *a; /**< One boolean expression */
struct boolexp_node *b; /**< Another boolean expression */
} sub;
struct boolexp_node *n; /**< Not locks: boolean expression to negate */
struct boolatr *atr_lock; /**< Atr, eval and flag locks */
const char *ind_lock; /**< Indirect locks */
} data;
};
/** The opcodes supported by the boolexp virtual machine. */
typedef enum bvm_opcode {
OP_JMPT, /**< Jump to ARG if R is true */
OP_JMPF, /**< Jump to ARG if R is false */
OP_TCONST, /**< Tests plain #ARG */
OP_TATR, /**< Tests S:ARG */
OP_TIND, /**< Tests @#ARG/S */
OP_TCARRY, /**< Tests +#ARG */
OP_TIS, /**< Tests =#ARG */
OP_TOWNER, /**< Tests $#ARG */
OP_TEVAL, /**< Tests S/ARG */
OP_TFLAG, /**< Tests FLAG^ARG */
OP_TTYPE, /**< Tests TYPE^ARG */
OP_TPOWER, /**< Tests POWER^ARG */
OP_TCHANNEL, /**< Tests CHANNEL^ARG */
OP_TIP, /**< Tests IP^ARG */
OP_THOSTNAME, /**< Tests HOSTNAME^ARG */
OP_TOBJID, /**< Tests OBJID^ARG */
OP_LOADS, /**< Load ARG into S */
OP_LOADR, /**< Load ARG into R */
OP_NEGR, /**< Negate R */
OP_PAREN, /**< ARG = 0 for a (, ARG = 1 for a ) in decompiling */
OP_LABEL, /**< A label. Not actually in compiled bytecode */
OP_RET /**< Stop evaluating bytecode */
} bvm_opcode;
/** The size of a single bytecode instruction. Probably 5 bytes
* everywhere. */
#define INSN_LEN (1 + sizeof(int))
/** Information describing one VM instruction or label in the
* intermediate "assembly" generated from a parse tree. The nodes are
* part of a linked list. */
struct bvm_asmnode {
bvm_opcode op; /**< The opcode */
int arg; /**< The arg value, or a label or string number */
struct bvm_asmnode *next; /**< Pointer to the next node */
};
/** Information describing a string to emit in the string section of
* the bytecode. The nodes are part of a linked list. */
struct bvm_strnode {
char *s; /**< The string */
int len; /**< Its length */
struct bvm_strnode *next; /**< Pointer to the next node */
};
/** A struct describing the complete assembly information needed to
* generate bytecode */
struct bvm_asm {
struct bvm_asmnode *head; /**< The start of the list of assembly instructions */
struct bvm_asmnode *tail; /**< The end of the list */
struct bvm_strnode *shead; /**< The start of the list of strings */
struct bvm_strnode *stail; /**< The end of the list */
int label; /**< The current label id to use */
int strcount; /**< The number of nodes in the string list */
};
/** The flag lock key (A^B) only allows a few values for A. This
* struct and the the following table define the allowable ones. When
* adding a new type here, a matching new bytecode instruction should
* be added. */
struct flag_lock_types {
const char *name; /**< The value of A */
bvm_opcode op; /**< The associated opcode */
};
/** What's allowed on the left-hand-side of LHS^RHS lock keys */
static struct flag_lock_types flag_locks[] = {
{"FLAG", OP_TFLAG},
{"POWER", OP_TPOWER},
{"TYPE", OP_TTYPE},
{"CHANNEL", OP_TCHANNEL},
{"OBJID", OP_TOBJID},
{"IP", OP_TIP},
{"HOSTNAME", OP_THOSTNAME},
{NULL, 0}
};
static char *
safe_get_bytecode(boolexp b)
__attribute_malloc__;
static char *get_bytecode(boolexp b, u_int_16 * storelen);
static struct boolexp_node *alloc_bool(void) __attribute_malloc__;
static struct boolatr *alloc_atr(const char *name,
const char *s) __attribute_malloc__;
static void skip_whitespace(void);
static void free_bool(struct boolexp_node *b);
static struct boolexp_node *test_atr(char *s, char c);
static struct boolexp_node *parse_boolexp_R(void);
static struct boolexp_node *parse_boolexp_L(void);
static struct boolexp_node *parse_boolexp_O(void);
static struct boolexp_node *parse_boolexp_C(void);
static struct boolexp_node *parse_boolexp_I(void);
static struct boolexp_node *parse_boolexp_A(void);
static struct boolexp_node *parse_boolexp_F(void);
static struct boolexp_node *parse_boolexp_T(void);
static struct boolexp_node *parse_boolexp_E(void);
static int check_attrib_lock(dbref player, dbref target,
const char *atrname, const char *str);
static void free_boolexp_node(struct boolexp_node *b);
static int gen_label_id(struct bvm_asm *a);
static void append_insn(struct bvm_asm *a, bvm_opcode op, int arg,
const char *s);
static struct bvm_asm *generate_bvm_asm(struct boolexp_node *b)
__attribute_malloc__;
static void generate_bvm_asm1(struct bvm_asm *a, struct boolexp_node *b,
boolexp_type outer);
static int pos_of_label(struct bvm_asm *a, int label);
static int offset_to_string(struct bvm_asm *a, int c);
static struct bvm_asmnode *insn_after_label(struct bvm_asm *a, int label);
static void opt_thread_jumps(struct bvm_asm *a);
static void optimize_bvm_asm(struct bvm_asm *a);
static boolexp emit_bytecode(struct bvm_asm *a, int derefs);
static void free_bvm_asm(struct bvm_asm *a);
#ifdef DEBUG_BYTECODE
static int sizeof_boolexp_node(struct boolexp_node *b);
static void print_bytecode(boolexp b);
#endif
extern void complain
(dbref player, dbref i, const char *name, const char *desc, ...)
__attribute__ ((__format__(__printf__, 4, 5)));
void check_lock(dbref player, dbref i, const char *name, boolexp be);
int warning_lock_type(const boolexp l);
/** String tree of attribute names. Used in the parse tree. Might go
* away as the trees aren't persistant any more. */
extern StrTree atr_names;
/** String tree of lock names. Used in the parse tree. Might go away
* as the trees aren't persistant any more. */
extern StrTree lock_names;
/** Are we currently loading the db? If so, we avoid certain checks that
* would create a circularity.
*/
extern int loading_db;
/** Given a chunk id, return the bytecode for a boolexp.
* \param b the boolexp to retrieve.
* \return a malloced copy of the bytecode.
*/
static char *safe_get_bytecode(boolexp b)
{
char *bytecode;
u_int_16 len;
len = chunk_len(b);
bytecode = mush_malloc(len, "boolexp.bytecode");
chunk_fetch(b, bytecode, len);
return bytecode;
}
/** Given a chunk id, return the bytecode for a boolexp.
* \param b The boolexp to retrieve.
* \return a static copy of the bytecode.
*/
static char *
get_bytecode(boolexp b, u_int_16 * storelen)
{
static char bytecode[BUFFER_LEN * 2];
u_int_16 len;
len = chunk_fetch(b, bytecode, sizeof bytecode);
if (storelen)
*storelen = len;
return bytecode;
}
/* Public functions */
/** Copy a boolexp.
* This function makes a copy of a boolexp, allocating new memory for
* the copy.
* \param b a boolexp to copy.
* \return an allocated copy of the boolexp.
*/
boolexp
dup_bool(boolexp b)
{
boolexp r;
char *bytecode;
u_int_16 len = 0;
if (b == TRUE_BOOLEXP)
return TRUE_BOOLEXP;
bytecode = get_bytecode(b, &len);
r = chunk_create(bytecode, len, 1);
return r;
}
/** Free a boolexp
* This function deallocates a boolexp
* \param b a boolexp to delete
*/
void
free_boolexp(boolexp b)
{
if (b != TRUE_BOOLEXP)
chunk_delete(b);
}
/** Determine the memory usage of a boolexp.
* This function computes the total memory usage of a boolexp.
* \param b boolexp to analyze.
* \return size of boolexp in bytes.
*/
int
sizeof_boolexp(boolexp b)
{
if (b == TRUE_BOOLEXP)
return 0;
else
return chunk_len(b);
}
/** Evaluate a boolexp.
* This is the main function to be called by other hardcode. It
* determines whether a player can pass a boolexp lock on a given
* object.
* \param player the player trying to pass the lock.
* \param b the boolexp to evaluate.
* \param target the object with the lock.
* \retval 0 player fails to pass lock.
* \retval 1 player successfully passes lock.
*/
int
eval_boolexp(dbref player /* The player trying to pass */ ,
boolexp b /* The boolexp */ ,
dbref target /* The object with the lock */ )
{
static int boolexp_recursion = 0;
if (!GoodObject(player))
return 0;
if (boolexp_recursion > MAX_DEPTH) {
notify(player, T("Too much recursion in lock!"));
return 0;
}
if (b == TRUE_BOOLEXP) {
return 1;
} else {
bvm_opcode op;
int arg;
ATTR *a;
int r = 0;
char *s = NULL, *bytecode, *pc;
bytecode = pc = safe_get_bytecode(b);
while (1) {
op = (bvm_opcode) * pc;
memcpy(&arg, pc + 1, sizeof arg);
pc += INSN_LEN;
switch (op) {
case OP_RET:
goto done;
case OP_JMPT:
if (r)
pc = bytecode + arg;
break;
case OP_JMPF:
if (!r)
pc = bytecode + arg;
break;
case OP_LABEL:
case OP_PAREN:
break;
case OP_LOADS:
s = bytecode + arg;
break;
case OP_LOADR:
r = arg;
break;
case OP_NEGR:
r = !r;
break;
case OP_TCONST:
r = (GoodObject(arg)
&& !IsGarbage(arg)
&& (arg == player || member(arg, Contents(player))));
break;
case OP_TIS:
r = (GoodObject(arg)
&& !IsGarbage(arg)
&& arg == player);
break;
case OP_TCARRY:
r = (GoodObject(arg)
&& !IsGarbage(arg)
&& member(arg, Contents(player)));
break;
case OP_TOWNER:
r = (GoodObject(arg)
&& !IsGarbage(arg)
&& Owner(arg) == Owner(player));
break;
case OP_TIND:
/* We only allow evaluation of indirect locks if target can run
* the lock on the referenced object.
*/
boolexp_recursion++;
if (!GoodObject(arg) || IsGarbage(arg))
r = 0;
else if (!Can_Read_Lock(target, arg, s))
r = 0;
else
r = eval_boolexp(player, getlock(arg, s), arg);
boolexp_recursion--;
break;
case OP_TATR:
boolexp_recursion++;
a = atr_get(player, s);
if (!a || !Can_Read_Attr(target, player, a))
r = 0;
else {
char tbuf[BUFFER_LEN];
strcpy(tbuf, atr_value(a));
r = local_wild_match(bytecode + arg, tbuf);
}
boolexp_recursion--;
break;
case OP_TEVAL:
boolexp_recursion++;
r = check_attrib_lock(player, target, s, bytecode + arg);
boolexp_recursion--;
break;
case OP_TFLAG:
/* Note that both fields of a boolattr struct are upper-cased */
if (sees_flag("FLAG", target, player, bytecode + arg))
r = 1;
else
r = 0;
break;
case OP_TPOWER:
if (sees_flag("POWER", target, player, bytecode + arg))
r = 1;
else
r = 0;
break;
case OP_TOBJID:
{
dbref d;
d = parse_objid(bytecode + arg);
r = (player == d);
break;
}
case OP_TCHANNEL:
{
CHAN *chan;
boolexp_recursion++;
find_channel(bytecode + arg, &chan, target);
r = chan && onchannel(player, chan);
boolexp_recursion--;
}
break;
case OP_TIP:
boolexp_recursion++;
if (!Connected(Owner(player)))
r = 0;
else {
/* We use the attribute for permission checks, but we
* do the actual boolexp itself with the least idle
* descriptor's ip address.
*/
a = atr_get(Owner(player), "LASTIP");
if (!a || !Can_Read_Attr(target, player, a))
r = 0;
else {
char *p = least_idle_ip(Owner(player));
r = p ? quick_wild(bytecode + arg, p) : 0;
}
}
boolexp_recursion--;
break;
case OP_THOSTNAME:
boolexp_recursion++;
if (!Connected(Owner(player)))
r = 0;
else {
/* See comment for OP_TIP */
a = atr_get(Owner(player), "LASTSITE");
if (!a || !Can_Read_Attr(target, player, a))
r = 0;
else {
char *p = least_idle_hostname(Owner(player));
r = p ? quick_wild(bytecode + arg, p) : 0;
}
}
boolexp_recursion--;
break;
case OP_TTYPE:
switch (bytecode[arg]) {
case 'R':
r = Typeof(player) == TYPE_ROOM;
break;
case 'E':
r = Typeof(player) == TYPE_EXIT;
break;
case 'T':
r = Typeof(player) == TYPE_THING;
break;
case 'P':
r = Typeof(player) == TYPE_PLAYER;
break;
}
break;
default:
do_log(LT_ERR, 0, 0, "Bad boolexp opcode %d %d in object #%d",
op, arg, target);
report();
r = 0;
}
}
done:
mush_free(bytecode, "boolexp.bytecode");
return r;
}
}
/** Pretty-print object references for unparse_boolexp().
* \param player the object seeing the decompiled lock.
* \param thing the object referenced in the lock.
* \param flag How to print thing.
* \param buff The start of the output buffer.
* \param bp Pointer to the current position in buff.
* \return 0 on success, true on buffer overflow.
*/
static int
safe_boref(dbref player, dbref thing, enum u_b_f flag, char *buff, char **bp)
{
switch (flag) {
case UB_MEREF:
if (player == thing)
return safe_strl("me", 2, buff, bp);
else
return safe_dbref(thing, buff, bp);
case UB_DBREF:
return safe_dbref(thing, buff, bp);
case UB_ALL:
default:
return safe_str(unparse_object(player, thing), buff, bp);
}
}
/** Display a boolexp.
* This function returns the textual representation of the boolexp.
* \param player The object wanting the decompiled boolexp.
* \param b The boolexp to decompile.
* \param flag How to format objects in the result.
* \return a static string with the decompiled boolexp.
*/
char *
unparse_boolexp(dbref player, boolexp b, enum u_b_f flag)
{
static char boolexp_buf[BUFFER_LEN];
char *buftop = boolexp_buf;
char *bytecode = NULL;
if (b == TRUE_BOOLEXP)
safe_str("*UNLOCKED*", boolexp_buf, &buftop);
else {
bvm_opcode op;
int arg;
char *pc;
char *s = NULL;
bytecode = pc = get_bytecode(b, NULL);
while (1) {
op = (bvm_opcode) * pc;
memcpy(&arg, pc + 1, sizeof arg);
pc += INSN_LEN;
/* Handle most negation cases */
if (op != OP_RET && (bvm_opcode) * pc == OP_NEGR && op != OP_PAREN)
safe_chr('!', boolexp_buf, &buftop);
switch (op) {
case OP_JMPT:
safe_chr('|', boolexp_buf, &buftop);
break;
case OP_JMPF:
safe_chr('&', boolexp_buf, &buftop);
break;
case OP_RET:
goto done;
case OP_LABEL: /* Will never happen, but shuts up the compiler */
case OP_NEGR:
break;
case OP_LOADS:
s = bytecode + arg;
break;
case OP_LOADR:
if (arg)
safe_str("#TRUE", boolexp_buf, &buftop);
else
safe_str("#FALSE", boolexp_buf, &buftop);
break;
case OP_PAREN:
if (arg == 0) {
int pstack = 1, parg;
char *tpc = pc;
while (1) {
if ((bvm_opcode) * tpc == OP_PAREN) {
memcpy(&parg, tpc + 1, sizeof parg);
if (parg)
pstack--;
else
pstack++;
if (pstack == 0) {
tpc += INSN_LEN;
break;
}
}
tpc += INSN_LEN;
}
if ((bvm_opcode) * tpc == OP_NEGR)
safe_strl("!(", 2, boolexp_buf, &buftop);
else
safe_chr('(', boolexp_buf, &buftop);
} else if (arg == 1)
safe_chr(')', boolexp_buf, &buftop);
break;
case OP_TCONST:
safe_boref(player, arg, flag, boolexp_buf, &buftop);
break;
case OP_TATR:
safe_format(boolexp_buf, &buftop, "%s:%s", s, bytecode + arg);
break;
case OP_TIND:
safe_chr(AT_TOKEN, boolexp_buf, &buftop);
safe_boref(player, arg, flag, boolexp_buf, &buftop);
safe_format(boolexp_buf, &buftop, "/%s", s);
break;
case OP_TCARRY:
safe_chr(IN_TOKEN, boolexp_buf, &buftop);
safe_boref(player, arg, flag, boolexp_buf, &buftop);
break;
case OP_TIS:
safe_chr(IS_TOKEN, boolexp_buf, &buftop);
safe_boref(player, arg, flag, boolexp_buf, &buftop);
break;
case OP_TOWNER:
safe_chr(OWNER_TOKEN, boolexp_buf, &buftop);
safe_boref(player, arg, flag, boolexp_buf, &buftop);
break;
case OP_TEVAL:
safe_format(boolexp_buf, &buftop, "%s/%s", s, bytecode + arg);
break;
case OP_TFLAG:
safe_format(boolexp_buf, &buftop, "FLAG^%s", bytecode + arg);
break;
case OP_TTYPE:
safe_format(boolexp_buf, &buftop, "TYPE^%s", bytecode + arg);
break;
case OP_TPOWER:
safe_format(boolexp_buf, &buftop, "POWER^%s", bytecode + arg);
break;
case OP_TOBJID:
safe_format(boolexp_buf, &buftop, "OBJID^%s", bytecode + arg);
break;
case OP_TCHANNEL:
safe_format(boolexp_buf, &buftop, "CHANNEL^%s", bytecode + arg);
break;
case OP_TIP:
safe_format(boolexp_buf, &buftop, "IP^%s", bytecode + arg);
break;
case OP_THOSTNAME:
safe_format(boolexp_buf, &buftop, "HOSTNAME^%s", bytecode + arg);
break;
}
}
}
done:
*buftop++ = '\0';
return boolexp_buf;
}
/* Parser and parse-tree related functions. If the parser returns NULL, you lose */
/** The source string for the lock we're parsing */
static const char *parsebuf;
/** The player from whose perspective we're parsing */
static dbref parse_player;
/** The name of the lock we're parsing */
static lock_type parse_ltype;
/** Allocate a boolatr for a parse tree node.
* \param name the name of the attribute.
* \param s a pattern to match against.
* \return a newly allocated boolatr.
*/
static struct boolatr *
alloc_atr(const char *name, const char *s)
{
struct boolatr *a;
size_t len;
if (s)
len = strlen(s) + 1;
else
len = 1;
a = (struct boolatr *)
mush_malloc(sizeof(struct boolatr) - BUFFER_LEN + len, "boolatr");
if (!a)
return NULL;
a->name = st_insert(strupper(name), &atr_names);
if (!a->name) {
mush_free(a, "boolatr");
return NULL;
}
if (s)
memcpy(a->text, s, len);
else
a->text[0] = '\0';
return a;
}
/** Returns a new boolexp_node for the parse tree.
* \return a new newly allocated boolexp_node.
*/
static struct boolexp_node *
alloc_bool(void)
{
struct boolexp_node *b;
b = mush_malloc(sizeof *b, "boolexp_node");
b->data.sub.a = NULL;
b->data.sub.b = NULL;
b->thing = NOTHING;
return b;
}
/** Frees a boolexp node.
* \param b the boolexp_node to deallocate.
*/
static void
free_bool(struct boolexp_node *b)
{
mush_free(b, "boolexp_node");
}
/** Free a boolexp ast node.
* This function frees a boolexp, including all subexpressions,
* recursively.
* \param b boolexp to free.
*/
static void
free_boolexp_node(struct boolexp_node *b)
{
if (b) {
switch (b->type) {
case BOOLEXP_AND:
case BOOLEXP_OR:
free_boolexp_node(b->data.sub.a);
free_boolexp_node(b->data.sub.b);
free_bool(b);
break;
case BOOLEXP_NOT:
free_boolexp_node(b->data.n);
free_bool(b);
break;
case BOOLEXP_CONST:
case BOOLEXP_CARRY:
case BOOLEXP_IS:
case BOOLEXP_OWNER:
case BOOLEXP_BOOL:
free_bool(b);
break;
case BOOLEXP_IND:
if (b->data.ind_lock)
st_delete(b->data.ind_lock, &lock_names);
free_bool(b);
break;
case BOOLEXP_ATR:
case BOOLEXP_EVAL:
case BOOLEXP_FLAG:
if (b->data.atr_lock) {
if (b->data.atr_lock->name)
st_delete(b->data.atr_lock->name, &atr_names);
mush_free((Malloc_t) b->data.atr_lock, "boolatr");
}
free_bool(b);
break;
}
}
}
/** Skip over leading whitespace characters in parsebuf */
static void
skip_whitespace(void)
{
while (*parsebuf && isspace((unsigned char) *parsebuf))
parsebuf++;
}
static struct boolexp_node *
test_atr(char *s, char c)
{
struct boolexp_node *b;
char tbuf1[BUFFER_LEN];
strcpy(tbuf1, strupper(s));
for (s = tbuf1; *s && (*s != c); s++) ;
if (!*s)
return 0;
*s++ = 0;
if (strlen(tbuf1) == 0 || !good_atr_name(tbuf1))
return 0;
if (c == '^') {
int n;
for (n = 0; flag_locks[n].name; n++) {
if (strcmp(flag_locks[n].name, tbuf1) == 0)
break;
}
if (!flag_locks[n].name)
return 0;
}
b = alloc_bool();
if (c == ':')
b->type = BOOLEXP_ATR;
else if (c == '/')
b->type = BOOLEXP_EVAL;
else if (c == '^')
b->type = BOOLEXP_FLAG;
b->data.atr_lock = alloc_atr(tbuf1, s);
return b;
}
/* L -> E, L is an object name or dbref or #t* or #f* */
static struct boolexp_node *
parse_boolexp_R(void)
{
struct boolexp_node *b;
char tbuf1[BUFFER_LEN];
char *p;
b = alloc_bool();
b->type = BOOLEXP_CONST;
p = tbuf1;
while (*parsebuf
&& *parsebuf != AND_TOKEN && *parsebuf != '/'
&& *parsebuf != OR_TOKEN && *parsebuf != ')') {
*p++ = *parsebuf++;
}
/* strip trailing whitespace */
*p-- = '\0';
while (isspace((unsigned char) *p))
*p-- = '\0';
/* do the match */
if (loading_db) {
if (*tbuf1 == '#' && *(tbuf1 + 1)) {
if (*(tbuf1 + 1) == 't' || *(tbuf1 + 1) == 'T') {
b->type = BOOLEXP_BOOL;
b->thing = 1;
} else if (*(tbuf1 + 1) == 'f' || *(tbuf1 + 1) == 'F') {
b->type = BOOLEXP_BOOL;
b->thing = 0;
} else {
b->thing = parse_integer(tbuf1 + 1);
}
} else {
/* Ooog. Dealing with a malformed lock in the database. */
free_bool(b);
return NULL;
}
return b;
} else {
/* Are these special atoms? */
if (*tbuf1 && *tbuf1 == '#' && *(tbuf1 + 1)) {
if (*(tbuf1 + 1) == 't' || *(tbuf1 + 1) == 'T') {
b->type = BOOLEXP_BOOL;
b->thing = 1;
return b;
} else if (*(tbuf1 + 1) == 'f' || *(tbuf1 + 1) == 'F') {
b->type = BOOLEXP_BOOL;
b->thing = 0;
return b;
}
}
b->thing = match_result(parse_player, tbuf1, TYPE_THING, MAT_EVERYTHING);
if (b->thing == NOTHING) {
notify_format(parse_player, T("I don't see %s here."), tbuf1);
free_bool(b);
return NULL;
} else if (b->thing == AMBIGUOUS) {
notify_format(parse_player, T("I don't know which %s you mean!"), tbuf1);
free_bool(b);
return NULL;
} else {
return b;
}
}
}
/* L -> (E); L -> eval/attr/flag lock, (lock) */
static struct boolexp_node *
parse_boolexp_L(void)
{
struct boolexp_node *b;
char *p;
const char *savebuf;
char tbuf1[BUFFER_LEN];
skip_whitespace();
switch (*parsebuf) {
case '(':
parsebuf++;
b = parse_boolexp_E();
skip_whitespace();
if (b == NULL || *parsebuf++ != ')') {
free_boolexp_node(b);
return NULL;
} else {
return b;
}
/* break; */
default:
/* must have hit an object ref */
/* load the name into our buffer */
p = tbuf1;
savebuf = parsebuf;
while (*parsebuf
&& *parsebuf != AND_TOKEN
&& *parsebuf != OR_TOKEN && *parsebuf != ')') {
*p++ = *parsebuf++;
}
/* strip trailing whitespace */
*p-- = '\0';
while (isspace((unsigned char) *p))
*p-- = '\0';
/* check for an attribute */
b = test_atr(tbuf1, ':');
if (b)
return b;
/* check for an eval */
b = test_atr(tbuf1, '/');
if (b)
return b;
/* Check for a flag */
b = test_atr(tbuf1, '^');
if (b)
return b;
/* Nope. Check for an object reference */
parsebuf = savebuf;
return parse_boolexp_R();
}
}
/* O -> $Identifier ; O -> L */
static struct boolexp_node *
parse_boolexp_O(void)
{
struct boolexp_node *b2, *t;
skip_whitespace();
if (*parsebuf == OWNER_TOKEN) {
parsebuf++;
b2 = alloc_bool();
b2->type = BOOLEXP_OWNER;
t = parse_boolexp_R();
if (t == NULL) {
free_boolexp_node(b2);
return NULL;
} else {
b2->thing = t->thing;
free_boolexp_node(t);
return b2;
}
}
return parse_boolexp_L();
}
/* C -> +Identifier ; C -> O */
static struct boolexp_node *
parse_boolexp_C(void)
{
struct boolexp_node *b2, *t;
skip_whitespace();
if (*parsebuf == IN_TOKEN) {
parsebuf++;
b2 = alloc_bool();
b2->type = BOOLEXP_CARRY;
t = parse_boolexp_R();
if (t == NULL) {
free_boolexp_node(b2);
return NULL;
} else {
b2->thing = t->thing;
free_boolexp_node(t);
return b2;
}
}
return parse_boolexp_O();
}
/* I -> =Identifier ; I -> C */
static struct boolexp_node *
parse_boolexp_I(void)
{
struct boolexp_node *b2, *t;
skip_whitespace();
if (*parsebuf == IS_TOKEN) {
parsebuf++;
b2 = alloc_bool();
b2->type = BOOLEXP_IS;
t = parse_boolexp_R();
if (t == NULL) {
free_boolexp_node(b2);
return NULL;
} else {
b2->thing = t->thing;
free_boolexp_node(t);
return b2;
}
}
return parse_boolexp_C();
}
/* A -> @L; A -> I */
static struct boolexp_node *
parse_boolexp_A(void)
{
struct boolexp_node *b2, *t;
skip_whitespace();
if (*parsebuf == AT_TOKEN) {
parsebuf++;
b2 = alloc_bool();
b2->type = BOOLEXP_IND;
t = parse_boolexp_R();
if (t == NULL) {
free_boolexp_node(b2);
return NULL;
}
b2->thing = t->thing;
free_boolexp_node(t);
if (*parsebuf == '/') {
char tbuf1[BUFFER_LEN], *p;
const char *m;
parsebuf++;
p = tbuf1;
while (*parsebuf
&& *parsebuf != AND_TOKEN
&& *parsebuf != OR_TOKEN && *parsebuf != ')') {
*p++ = *parsebuf++;
}
/* strip trailing whitespace */
*p-- = '\0';
while (isspace((unsigned char) *p))
*p-- = '\0';
upcasestr(tbuf1);
if (!good_atr_name(tbuf1)) {
free_boolexp_node(b2);
return NULL;
}
m = match_lock(tbuf1);
b2->data.ind_lock = st_insert(m ? m : tbuf1, &lock_names);
} else {
b2->data.ind_lock = st_insert(parse_ltype, &atr_names);
}
return b2;
}
return parse_boolexp_I();
}
/* F -> !F;F -> A */
static struct boolexp_node *
parse_boolexp_F(void)
{
struct boolexp_node *b2;
skip_whitespace();
if (*parsebuf == NOT_TOKEN) {
parsebuf++;
b2 = alloc_bool();
b2->type = BOOLEXP_NOT;
if ((b2->data.n = parse_boolexp_F()) == NULL) {
free_boolexp_node(b2);
return NULL;
} else
return b2;
}
return parse_boolexp_A();
}
/* T -> F; T -> F & T */
static struct boolexp_node *
parse_boolexp_T(void)
{
struct boolexp_node *b, *b2;
if ((b = parse_boolexp_F()) == NULL) {
return b;
} else {
skip_whitespace();
if (*parsebuf == AND_TOKEN) {
parsebuf++;
b2 = alloc_bool();
b2->type = BOOLEXP_AND;
b2->data.sub.a = b;
if ((b2->data.sub.b = parse_boolexp_T()) == NULL) {
free_boolexp_node(b2);
return NULL;
} else {
return b2;
}
} else {
return b;
}
}
}
/* E -> T; E -> T | E */
static struct boolexp_node *
parse_boolexp_E(void)
{
struct boolexp_node *b, *b2;
if ((b = parse_boolexp_T()) == NULL) {
return b;
} else {
skip_whitespace();
if (*parsebuf == OR_TOKEN) {
parsebuf++;
b2 = alloc_bool();
b2->type = BOOLEXP_OR;
b2->data.sub.a = b;
if ((b2->data.sub.b = parse_boolexp_E()) == NULL) {
free_boolexp_node(b2);
return NULL;
} else {
return b2;
}
} else {
return b;
}
}
}
/* Functions for turning the parse tree into assembly */
/** Create a label identifier.
* \param a the assembler list the label is for.
* \return a new label id
*/
static int
gen_label_id(struct bvm_asm *a)
{
int l = a->label;
a->label++;
return l;
}
/** Add an instruction to the assembler list.
* \param a the assembler list.
* \param op the opcode of the instruction.
* \param arg the argument for the instruction if numeric.
* \param s the string to use as the argument for the instruction. If non-NULL, arg's value is ignored.
*/
static void
append_insn(struct bvm_asm *a, bvm_opcode op, int arg, const char *s)
{
struct bvm_asmnode *newop;
if (s) {
struct bvm_strnode *newstr;
int count = 0, found = 0;
/* Look for an existing string */
for (newstr = a->shead; newstr; newstr = newstr->next, count++) {
if (strcmp(newstr->s, s) == 0) {
arg = count;
found = 1;
break;
}
}
/* Allocate a new string if needed. */
if (!found) {
newstr = mush_malloc(sizeof *newstr, "bvm.strnode");
if (!s)
mush_panic(T("Unable to allocate memory for boolexp string node!"));
newstr->s = mush_strdup(s, "bvm.string");
if (!newstr->s)
mush_panic(T("Unable to allocate memory for boolexp string!"));
newstr->len = strlen(s) + 1;
newstr->next = NULL;
if (a->shead == NULL)
a->shead = a->stail = newstr;
else {
a->stail->next = newstr;
a->stail = newstr;
}
arg = a->strcount;
a->strcount++;
}
}
newop = mush_malloc(sizeof *newop, "bvm.asmnode");
if (!newop)
mush_panic(T("Unable to allocate memory for boolexp asm node!"));
newop->op = op;
newop->arg = arg;
newop->next = NULL;
if (a->head == NULL)
a->head = a->tail = newop;
else {
a->tail->next = newop;
a->tail = newop;
}
}
/** Does the actual work of walking the parse tree and creating an
* assembler list from it.
* \param a the assembler list.
* \param b the root of the parse tree.
* \param outer the type of root's parent node.
*/
static void
generate_bvm_asm1(struct bvm_asm *a, struct boolexp_node *b, boolexp_type outer)
{
int lbl;
switch (b->type) {
case BOOLEXP_AND:
lbl = gen_label_id(a);
if (outer == BOOLEXP_NOT)
append_insn(a, OP_PAREN, 0, NULL);
generate_bvm_asm1(a, b->data.sub.a, b->type);
append_insn(a, OP_JMPF, lbl, NULL);
generate_bvm_asm1(a, b->data.sub.b, b->type);
if (outer == BOOLEXP_NOT)
append_insn(a, OP_PAREN, 1, NULL);
append_insn(a, OP_LABEL, lbl, NULL);
break;
case BOOLEXP_OR:
lbl = gen_label_id(a);
if (outer == BOOLEXP_NOT || outer == BOOLEXP_AND)
append_insn(a, OP_PAREN, 0, NULL);
generate_bvm_asm1(a, b->data.sub.a, b->type);
append_insn(a, OP_JMPT, lbl, NULL);
generate_bvm_asm1(a, b->data.sub.b, b->type);
if (outer == BOOLEXP_NOT || outer == BOOLEXP_AND)
append_insn(a, OP_PAREN, 1, NULL);
append_insn(a, OP_LABEL, lbl, NULL);
break;
case BOOLEXP_IND:
append_insn(a, OP_LOADS, 0, b->data.ind_lock);
append_insn(a, OP_TIND, b->thing, NULL);
break;
case BOOLEXP_IS:
append_insn(a, OP_TIS, b->thing, NULL);
break;
case BOOLEXP_CARRY:
append_insn(a, OP_TCARRY, b->thing, NULL);
break;
case BOOLEXP_OWNER:
append_insn(a, OP_TOWNER, b->thing, NULL);
break;
case BOOLEXP_NOT:
generate_bvm_asm1(a, b->data.n, b->type);
append_insn(a, OP_NEGR, 0, NULL);
break;
case BOOLEXP_CONST:
append_insn(a, OP_TCONST, b->thing, NULL);
break;
case BOOLEXP_BOOL:
append_insn(a, OP_LOADR, b->thing, NULL);
break;
case BOOLEXP_ATR:
append_insn(a, OP_LOADS, 0, b->data.atr_lock->name);
append_insn(a, OP_TATR, 0, b->data.atr_lock->text);
break;
case BOOLEXP_EVAL:
append_insn(a, OP_LOADS, 0, b->data.atr_lock->name);
append_insn(a, OP_TEVAL, 0, b->data.atr_lock->text);
break;
case BOOLEXP_FLAG:
{
enum bvm_opcode op = OP_RET;
int n;
for (n = 0; flag_locks[n].name; n++) {
if (strcmp(b->data.atr_lock->name, flag_locks[n].name) == 0) {
op = flag_locks[n].op;
break;
}
}
append_insn(a, op, 0, b->data.atr_lock->text);
break;
}
}
}
/** Turn a parse tree into an assembler list.
* \param the parse tree
* \return newly allocated assembler list.
*/
static struct bvm_asm *
generate_bvm_asm(struct boolexp_node *b)
{
struct bvm_asm *a;
if (!b)
return NULL;
a = mush_malloc(sizeof *a, "bvm.asm");
if (!a)
return NULL;
a->strcount = a->label = 0;
a->head = a->tail = NULL;
a->shead = a->stail = NULL;
generate_bvm_asm1(a, b, BOOLEXP_CONST);
append_insn(a, OP_RET, 0, NULL);
return a;
}
/** Frees an assembler list.
* \param a the assembler list to deallocate.
*/
static void
free_bvm_asm(struct bvm_asm *a)
{
struct bvm_asmnode *i, *tmp1;
struct bvm_strnode *s, *tmp2;
if (!a)
return;
for (i = a->head; i; i = tmp1) {
tmp1 = i->next;
mush_free(i, "bvm.asmnode");
}
for (s = a->shead; s; s = tmp2) {
tmp2 = s->next;
mush_free(s->s, "bvm.string");
mush_free(s, "bvm.strnode");
}
mush_free(a, "bvm.asm");
}
/** Find the position of a labeled instruction.
* \param as the assembler list.
* \param the id of the label to find.
* \return the number of instructions before the label.
*/
static int
pos_of_label(struct bvm_asm *as, int label)
{
int offset = 0;
struct bvm_asmnode *a;
for (a = as->head; a; a = a->next) {
if (a->op == OP_LABEL && a->arg == label)
return offset;
if (a->op != OP_LABEL)
offset++;
}
return offset; /* Never reached! */
}
/** Find the position of a string.
* \param a the assembler list
* \param c The c-th string is the one that's wanted.
* \return the distance from the start of the string section to the start of the c-th string.
*/
static int
offset_to_string(struct bvm_asm *a, int c)
{
int offset = 0;
int n = 0;
struct bvm_strnode *s;
for (s = a->shead; s; s = s->next, n++) {
if (n == c)
return offset;
else
offset += s->len;
}
return offset; /* Never reached! */
}
/** Find the next instruction after a label.
* \param a the assembler list.
* \param label the label id to look for.
* \return a pointer to the first real instruction after a label; where a jump to that label will go to.
*/
static struct bvm_asmnode *
insn_after_label(struct bvm_asm *a, int label)
{
struct bvm_asmnode *n;
for (n = a->head; n; n = n->next) {
if (n->op == OP_LABEL && n->arg == label) {
do {
n = n->next;
} while (n->op == OP_LABEL);
return n;
}
}
return NULL;
}
/** Avoid jumps that lead straight to another jump. If the second jump
* is on the same condition as the first one, jump instead to its
* destination. If it's the opposite condition, jump instead to the
* first instruction after the second jump to avoid the useless
* conditional check.
* \param a the assembler list to thread. */
static void
opt_thread_jumps(struct bvm_asm *a)
{
struct bvm_asmnode *n, *target;
for (n = a->head; n;) {
if (n->op == OP_JMPT || n->op == OP_JMPF) {
target = insn_after_label(a, n->arg);
if (target && (target->op == OP_JMPT || target->op == OP_JMPF)) {
if (target->op == n->op) {
/* Avoid daisy-chained conditional jumps on the same
condition.
*/
n->arg = target->arg;
} else {
/* Avoid useless conditional jumps on different conditions by
jumping to the next instruction after. Ex: a&b|c */
struct bvm_asmnode *newlbl;
newlbl = mush_malloc(sizeof *newlbl, "bvm.asmnode");
if (!newlbl)
mush_panic(T("Unable to allocate memory for boolexp asm node!"));
newlbl->op = OP_LABEL;
n->arg = newlbl->arg = gen_label_id(a);
if (target->next)
newlbl->next = target->next;
else
newlbl->next = NULL;
target->next = newlbl;
if (a->tail == target)
a->tail = newlbl;
}
} else
n = n->next;
} else
n = n->next;
}
}
/** Do some trivial optimizations.
* \param a the assembler list to transform.
*/
static void
optimize_bvm_asm(struct bvm_asm *a)
{
if (!a)
return;
opt_thread_jumps(a);
}
/** Turn assembly into bytecode.
* \param a the assembly list to emit.
* \param the compiled bytecode.
*/
static boolexp
emit_bytecode(struct bvm_asm *a, int derefs)
{
boolexp b;
struct bvm_asmnode *i;
struct bvm_strnode *s;
char *pc, *bytecode;
u_int_16 len, blen;
if (!a)
return TRUE_BOOLEXP;
/* Calculate the total size of the bytecode */
len = 0;
for (i = a->head; i; i = i->next) {
if (i->op == OP_LABEL)
continue;
len++;
}
len *= INSN_LEN;
blen = len;
for (s = a->shead; s; s = s->next)
len += s->len;
pc = bytecode = mush_malloc(len, "boolexp.bytecode");
if (!pc)
return TRUE_BOOLEXP;
/* Emit the instructions */
for (i = a->head; i; i = i->next) {
switch (i->op) {
case OP_LABEL:
continue;
case OP_JMPT:
case OP_JMPF:
i->arg = pos_of_label(a, i->arg) * INSN_LEN;
break;
case OP_LOADS:
case OP_TEVAL:
case OP_TATR:
case OP_TFLAG:
case OP_TPOWER:
case OP_TOBJID:
case OP_TTYPE:
case OP_TCHANNEL:
case OP_TIP:
case OP_THOSTNAME:
i->arg = blen + offset_to_string(a, i->arg);
break;
default:
break;
}
*pc = (char) i->op;
memcpy(pc + 1, &i->arg, sizeof i->arg);
pc += INSN_LEN;
}
/* Emit the strings section */
for (s = a->shead; s; s = s->next) {
memcpy(pc, s->s, s->len);
pc += s->len;
}
b = chunk_create(bytecode, len, derefs);
mush_free(bytecode, "boolexp.bytecode");
return b;
}
/** Compile a string into boolexp bytecode.
* Given a textual representation of a boolexp in a string, parse it into
* a syntax tree, compile to bytecode, and return a pointer to a boolexp
* structure.
* \param player the enactor.
* \param buf string representation of a boolexp.
* \param ltype the type of lock for which the boolexp is being parsed.
* \param derefs the starting deref count for chunk storage.
* \return pointer to a newly allocated boolexp.
*/
boolexp
parse_boolexp_d(dbref player, const char *buf, lock_type ltype, int derefs)
{
struct boolexp_node *ast;
struct bvm_asm *bvasm;
boolexp bytecode;
/* Parse */
parsebuf = buf;
parse_player = player;
parse_ltype = ltype;
ast = parse_boolexp_E();
if (!ast)
return TRUE_BOOLEXP;
bvasm = generate_bvm_asm(ast);
if (!bvasm) {
free_boolexp_node(ast);
return TRUE_BOOLEXP;
}
optimize_bvm_asm(bvasm);
bytecode = emit_bytecode(bvasm, derefs);
#ifdef DEBUG_BYTECODE
printf("\nSource string: \"%s\"\n", buf);
printf("Parse tree size: %d bytes\n", sizeof_boolexp_node(ast));
print_bytecode(bytecode);
#endif
free_boolexp_node(ast);
free_bvm_asm(bvasm);
return bytecode;
}
/** Compile a string into boolexp bytecode.
* Given a textual representation of a boolexp in a string, parse it into
* a syntax tree, compile to bytecode, and return a pointer to a boolexp
* structure.
* \param player the enactor.
* \param buf string representation of a boolexp.
* \param ltype the type of lock for which the boolexp is being parsed.
* \return pointer to a newly allocated boolexp.
*/
boolexp
parse_boolexp(dbref player, const char *buf, lock_type ltype)
{
return parse_boolexp_d(player, buf, ltype, 0);
}
/** Test to see if an eval lock passes, with a exact match.
* \param player the object attempting to pass the lock.
* \param target the object the lock is on.
* \param atrname the name of the attribute to evaluate on target.
* \param str What the attribute should evaluate to to succeed.
* \retval 1 the lock succeeds.
* \retval 0 the lock fails.
*/
static int
check_attrib_lock(dbref player, dbref target,
const char *atrname, const char *str)
{
ATTR *a;
char *asave;
const char *ap;
char buff[BUFFER_LEN], *bp;
char *preserve[NUMQ];
if (!atrname || !*atrname || !str || !*str)
return 0;
/* fail if there's no matching attribute */
a = atr_get(target, strupper(atrname));
if (!a)
return 0;
if (!Can_Read_Attr(target, target, a))
return 0;
asave = safe_atr_value(a);
/* perform pronoun substitution */
save_global_regs("check_attrib_lock_save", preserve);
bp = buff;
ap = asave;
process_expression(buff, &bp, &ap, target, player,
player, PE_DEFAULT, PT_DEFAULT, NULL);
*bp = '\0';
restore_global_regs("check_attrib_lock_save", preserve);
free(asave);
return !strcasecmp(buff, str);
}
#ifdef DEBUG_BYTECODE
/** Find the size of a parse tree node, recursively to count all child nodes.
* \param b the root of the parse tree.
* \return the size of the parse tree in bytes.
*/
static int
sizeof_boolexp_node(struct boolexp_node *b)
{
if (!b)
return 0;
switch (b->type) {
case BOOLEXP_CONST:
case BOOLEXP_IS:
case BOOLEXP_CARRY:
case BOOLEXP_OWNER:
case BOOLEXP_IND:
case BOOLEXP_BOOL:
return sizeof *b;
case BOOLEXP_NOT:
return sizeof *b + sizeof_boolexp_node(b->data.n);
case BOOLEXP_AND:
case BOOLEXP_OR:
return sizeof *b +
sizeof_boolexp_node(b->data.sub.a) + sizeof_boolexp_node(b->data.sub.b);
case BOOLEXP_ATR:
case BOOLEXP_EVAL:
case BOOLEXP_FLAG:
return sizeof *b + sizeof *b->data.atr_lock - BUFFER_LEN +
strlen(b->data.atr_lock->text) + 1;
default:
/* Broken lock */
return sizeof *b;
}
}
/** Print out a decompiled-to-assembly bytecode to stdout.
* \param b the boolexp to decompile.
*/
static void
print_bytecode(boolexp b)
{
bvm_opcode op;
int arg, len = 0, pos = 0;
char *pc, *bytecode;
if (b == TRUE_BOOLEXP) {
puts("NULL bytecode!");
return;
}
pc = bytecode = get_bytecode(b, &len);
printf("Total length of bytecode+strings: %d bytes\n", len);
while (1) {
op = (bvm_opcode) * pc;
memcpy(&arg, pc + 1, sizeof arg);
pc += INSN_LEN;
printf("%-5d ", pos);
pos++;
switch (op) {
case OP_RET:
puts("RET");
return;
case OP_PAREN:
printf("PAREN %c\n", (arg == 0) ? '(' : ((arg == 1) ? ')' : '!'));
break;
case OP_JMPT:
printf("JMPT %d\n", arg / INSN_LEN);
break;
case OP_JMPF:
printf("JMPF %d\n", arg / INSN_LEN);
break;
case OP_TCONST:
printf("TCONST #%d\n", arg);
break;
case OP_TCARRY:
printf("TCARRY #%d\n", arg);
break;
case OP_TIS:
printf("TIS #%d\n", arg);
break;
case OP_TOWNER:
printf("TOWNER #%d\n", arg);
break;
case OP_TIND:
printf("TIND #%d\n", arg);
break;
case OP_TATR:
printf("TATR \"%s\"\n", bytecode + arg);
break;
case OP_TEVAL:
printf("TEVAL \"%s\"\n", bytecode + arg);
break;
case OP_TFLAG:
printf("TFLAG \"%s\"\n", bytecode + arg);
break;
case OP_TPOWER:
printf("TPOWER \"%s\"\n", bytecode + arg);
break;
case OP_TOBJID:
printf("TOBJID \"%s\"\n", bytecode + arg);
break;
case OP_TTYPE:
printf("TTYPE \"%s\"\n", bytecode + arg);
break;
case OP_TCHANNEL:
printf("TCHANNEL \"%s\"\n", bytecode + arg);
break;
case OP_TIP:
printf("TIP \"%s\"\n", bytecode + arg);
break;
case OP_THOSTNAME:
printf("THOSTNAME \"%s\"\n", bytecode + arg);
break;
case OP_LOADS:
printf("LOADS \"%s\"\n", bytecode + arg);
break;
case OP_LOADR:
printf("LOADR %d\n", arg);
break;
case OP_NEGR:
puts("NEGR");
break;
default:
printf("Hmm: %d %d\n", op, arg);
}
}
}
#endif
/* Warnings-related stuff here because I don't want to export details
of the bytecode outside this file. */
#define W_UNLOCKED 0x1 /**< Returned if a boolexp is unlocked */
#define W_LOCKED 0x2 /**< Returned if a boolexp is locked */
/** Check to see if a lock is considered possibly unlocked or not.
* This is really simple-minded for efficiency. Basically, if it's *
* unlocked, it's unlocked. If it's locked to something starting with
* a specific db#, it's locked. Anything else, and we don't know.
* \param l the boolexp to check.
* \retval W_UNLOCKED the boolexp is unlocked.
* \retval W_LOCKED the boolexp is considered locked.
* \retval W_LOCKED|W_UNLOCKED the boolexp is in an unknown state.
*/
int
warning_lock_type(const boolexp l)
/* 0== unlocked. 1== locked, 2== sometimes */
{
if (l == TRUE_BOOLEXP)
return W_UNLOCKED;
/* Two instructions means one of the simple lock cases */
else if (sizeof_boolexp(l) == (INSN_LEN + INSN_LEN))
return W_LOCKED;
else
return W_LOCKED | W_UNLOCKED;
}
/** Check for lock-check @warnings.
* Things like non-existant attributes in eval locks, references to
* garbage objects, or indirect locks that aren't present or visible.
* \param player the object to report warnings to.
* \param i the object the lock is on.
* \param name the lock type.
* \param be the lock key.
*/
void
check_lock(dbref player, dbref i, const char *name, boolexp be)
{
char *pc, *bytecode;
bvm_opcode op;
int arg;
char *s = NULL;
bytecode = pc = get_bytecode(be, NULL);
while (1) {
op = (bvm_opcode) * pc;
memcpy(&arg, pc + 1, sizeof arg);
pc += INSN_LEN;
switch (op) {
case OP_RET:
return;
case OP_LOADS:
s = bytecode + arg;
break;
case OP_TCONST:
case OP_TCARRY:
case OP_TIS:
case OP_TOWNER:
if (!GoodObject(arg) || IsGarbage(arg))
complain(player, i, "lock-checks",
T("%s lock refers to garbage object"), name);
break;
case OP_TEVAL:
{
ATTR *a;
a = atr_get(i, s);
if (!a || !Can_Read_Attr(i, i, a))
complain(player, i, "lock-checks",
T
("%s lock has eval-lock that uses a nonexistant attribute '%s'."),
name, s);
}
break;
case OP_TIND:
if (!GoodObject(arg) || IsGarbage(arg))
complain(player, i, "lock-checks",
T("%s lock refers to garbage object"), name);
else if (!(Can_Read_Lock(i, arg, s) && getlock(arg, s) != TRUE_BOOLEXP))
complain(player, i, "lock-checks",
T("%s lock has indirect lock to %s/%s that it can't read"),
name, unparse_object(player, arg), s);
break;
default:
break;
}
}
}