root/usr/src/cmd/fm/eversholt/common/check.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * check.c -- routines for checking the prop tree
 *
 * this module provides semantic checks on the parse tree.  most of
 * these checks happen during the construction of the parse tree,
 * when the various tree_X() routines call the various check_X()
 * routines.  in a couple of special cases, a check function will
 * process the parse tree after it has been fully constructed.  these
 * cases are noted in the comments above the check function.
 */

#include <stdio.h>
#include "out.h"
#include "stable.h"
#include "literals.h"
#include "lut.h"
#include "tree.h"
#include "ptree.h"
#include "check.h"

static int check_reportlist(enum nodetype t, const char *s, struct node *np);
static int check_num(enum nodetype t, const char *s, struct node *np);
static int check_quote(enum nodetype t, const char *s, struct node *np);
static int check_action(enum nodetype t, const char *s, struct node *np);
static int check_num_func(enum nodetype t, const char *s, struct node *np);
static int check_fru_asru(enum nodetype t, const char *s, struct node *np);
static int check_engine(enum nodetype t, const char *s, struct node *np);
static int check_count(enum nodetype t, const char *s, struct node *np);
static int check_timeval(enum nodetype t, const char *s, struct node *np);
static int check_id(enum nodetype t, const char *s, struct node *np);
static int check_serd_method(enum nodetype t, const char *s, struct node *np);
static int check_serd_id(enum nodetype t, const char *s, struct node *np);
static int check_nork(struct node *np);
static void check_cycle_lhs(struct node *stmtnp, struct node *arrow);
static void check_cycle_lhs_try(struct node *stmtnp, struct node *lhs,
    struct node *rhs);
static void check_cycle_rhs(struct node *rhs);
static void check_proplists_lhs(enum nodetype t, struct node *lhs);

static struct {
        enum nodetype t;
        const char *name;
        int required;
        int (*checker)(enum nodetype t, const char *s, struct node *np);
        int outflags;
} Allowednames[] = {
        { T_FAULT, "FITrate", 0, check_num_func, O_ERR },
        { T_FAULT, "FRU", 0, check_fru_asru, O_ERR },
        { T_FAULT, "ASRU", 0, check_fru_asru, O_ERR },
        { T_FAULT, "message", 0, check_num_func, O_ERR },
        { T_FAULT, "retire", 0, check_num_func, O_ERR },
        { T_FAULT, "response", 0, check_num_func, O_ERR },
        { T_FAULT, "action", 0, check_action, O_ERR },
        { T_FAULT, "count", 0, check_count, O_ERR },
        { T_FAULT, "engine", 0, check_engine, O_ERR },
        { T_UPSET, "engine", 0, check_engine, O_ERR },
        { T_DEFECT, "FRU", 0, check_fru_asru, O_ERR },
        { T_DEFECT, "ASRU", 0, check_fru_asru, O_ERR },
        { T_DEFECT, "engine", 0, check_engine, O_ERR },
        { T_DEFECT, "FITrate", 0, check_num_func, O_ERR },
        { T_EREPORT, "poller", 0, check_id, O_ERR },
        { T_EREPORT, "delivery", 0, check_timeval, O_ERR },
        { T_EREPORT, "discard_if_config_unknown", 0, check_num, O_ERR },
        { T_SERD, "N", 1, check_num, O_ERR },
        { T_SERD, "T", 1, check_timeval, O_ERR },
        { T_SERD, "method", 0, check_serd_method, O_ERR },
        { T_SERD, "trip", 0, check_reportlist, O_ERR },
        { T_SERD, "FRU", 0, check_fru_asru, O_ERR },
        { T_SERD, "id", 0, check_serd_id, O_ERR },
        { T_ERROR, "ASRU", 0, check_fru_asru, O_ERR },
        { T_CONFIG, NULL, 0, check_quote, O_ERR },
        { 0, NULL, 0 },
};

void
check_init(void)
{
        int i;

        for (i = 0; Allowednames[i].t; i++)
                if (Allowednames[i].name != NULL)
                        Allowednames[i].name = stable(Allowednames[i].name);
}

void
check_fini(void)
{
}

/*ARGSUSED*/
void
check_report_combination(struct node *np)
{
        /* nothing to check for here.  poller is only prop and it is optional */
}

/*
 * check_path_iterators -- verify all iterators are explicit
 */
static void
check_path_iterators(struct node *np)
{
        if (np == NULL)
                return;

        switch (np->t) {
                case T_ARROW:
                        check_path_iterators(np->u.arrow.lhs);
                        check_path_iterators(np->u.arrow.rhs);
                        break;

                case T_LIST:
                        check_path_iterators(np->u.expr.left);
                        check_path_iterators(np->u.expr.right);
                        break;

                case T_EVENT:
                        check_path_iterators(np->u.event.epname);
                        break;

                case T_NAME:
                        if (np->u.name.child == NULL)
                                outfl(O_DIE, np->file, np->line,
                                    "internal error: check_path_iterators: "
                                    "unexpected implicit iterator: %s",
                                    np->u.name.s);
                        check_path_iterators(np->u.name.next);
                        break;

                default:
                        outfl(O_DIE, np->file, np->line,
                            "internal error: check_path_iterators: "
                            "unexpected type: %s",
                            ptree_nodetype2str(np->t));
        }
}

void
check_arrow(struct node *np)
{
        ASSERTinfo(np->t == T_ARROW, ptree_nodetype2str(np->t));

        if (np->u.arrow.lhs->t != T_ARROW &&
            np->u.arrow.lhs->t != T_LIST &&
            np->u.arrow.lhs->t != T_EVENT) {
                outfl(O_ERR,
                    np->u.arrow.lhs->file, np->u.arrow.lhs->line,
                    "%s not allowed on left-hand side of arrow",
                    ptree_nodetype2str(np->u.arrow.lhs->t));
        }

        if (!check_nork(np->u.arrow.nnp) ||
            !check_nork(np->u.arrow.knp))
                outfl(O_ERR, np->file, np->line,
                    "counts associated with propagation arrows "
                    "must be integers");

        check_path_iterators(np);
}

/*
 * make sure the nork values are valid.
 * Nork values must be "A" for all(T_NAME),
 * a number(T_NUM), or a simple
 * expression(T_SUB, T_ADD, T_MUL, T_DIV)
 */
static int
check_nork(struct node *np)
{
        int rval = 0;

        /* NULL means no nork value which is allowed */
        if (np == NULL) {
                rval = 1;
        }
        else
        {
                /* if the nork is a name it must be A for "All" */
                if (np->t == T_NAME)
                        if (*np->u.name.s == 'A')
                                return (1);

                /*  T_NUM allowed */
                if (np->t == T_NUM)
                        rval = 1;

                /*  simple expressions allowed */
                if (np->t == T_SUB ||
                    np->t == T_ADD ||
                    np->t == T_MUL ||
                    np->t == T_DIV)
                        rval = 1;
        }

        return (rval);
}

static int
check_reportlist(enum nodetype t, const char *s, struct node *np)
{
        if (np == NULL)
                return (1);
        else if (np->t == T_EVENT) {
                if (np->u.event.ename->u.name.t != N_EREPORT) {
                        outfl(O_ERR, np->file, np->line,
                            "%s %s property must begin with \"ereport.\"",
                            ptree_nodetype2str(t), s);
                } else if (tree_event2np_lut_lookup(Ereports, np) == NULL) {
                        outfl(O_ERR, np->file, np->line,
                            "%s %s property contains undeclared name",
                            ptree_nodetype2str(t), s);
                }
                check_type_iterator(np);
        } else if (np->t == T_LIST) {
                (void) check_reportlist(t, s, np->u.expr.left);
                (void) check_reportlist(t, s, np->u.expr.right);
        }
        return (1);
}

static int
check_num(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_NUM)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be a single number",
                    ptree_nodetype2str(t), s);
        return (1);
}

/*ARGSUSED1*/
static int
check_quote(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_QUOTE)
                outfl(O_ERR, np->file, np->line,
                    "%s properties must be quoted strings",
                    ptree_nodetype2str(t));
        return (1);
}

static int
check_action(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));

        if (np->t != T_FUNC)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be a function or list of functions",
                    ptree_nodetype2str(t), s);
        return (1);
}

static int
check_num_func(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_NUM && np->t != T_FUNC)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be a number or function",
                    ptree_nodetype2str(t), s);
        return (1);
}

static int
check_fru_asru(enum nodetype t, const char *s, struct node *np)
{
        ASSERT(s != NULL);

        /* make sure it is a node type T_NAME? */
        if (np->t == T_NAME) {
                if (s == L_ASRU) {
                        if (tree_name2np_lut_lookup_name(ASRUs, np) == NULL)
                                outfl(O_ERR, np->file, np->line,
                                    "ASRU property contains undeclared asru");
                } else if (s == L_FRU) {
                        if (tree_name2np_lut_lookup_name(FRUs, np) == NULL)
                                outfl(O_ERR, np->file, np->line,
                                    "FRU property contains undeclared fru");
                } else {
                        outfl(O_ERR, np->file, np->line,
                            "illegal property name in %s declaration: %s",
                            ptree_nodetype2str(t), s);
                }
                check_type_iterator(np);
        } else
                outfl(O_ERR, np->file, np->line,
                    "illegal type used for %s property: %s",
                    s, ptree_nodetype2str(np->t));
        return (1);
}

static int
check_engine(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_EVENT)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be an engine name "
                    "(i.e. serd.x or serd.x@a/b)",
                    ptree_nodetype2str(t), s);

        return (1);
}

static int
check_count(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_EVENT)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be an engine name "
                    "(i.e. stat.x or stat.x@a/b)",
                    ptree_nodetype2str(t), s);

        /* XXX confirm engine has been declared */
        return (1);
}

static int
check_timeval(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_TIMEVAL)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be a number with time units",
                    ptree_nodetype2str(t), s);
        return (1);
}

static int
check_id(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_NAME || np->u.name.next || np->u.name.child)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be simple name",
                    ptree_nodetype2str(t), s);
        return (1);
}

static int
check_serd_method(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_NAME || np->u.name.next || np->u.name.child ||
            (np->u.name.s != L_volatile &&
            np->u.name.s != L_persistent))
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be \"volatile\" or \"persistent\"",
                    ptree_nodetype2str(t), s);
        return (1);
}

static int
check_serd_id(enum nodetype t, const char *s, struct node *np)
{
        ASSERTinfo(np != NULL, ptree_nodetype2str(t));
        if (np->t != T_GLOBID)
                outfl(O_ERR, np->file, np->line,
                    "%s %s property must be a global ID",
                    ptree_nodetype2str(t), s);
        return (1);
}

void
check_stmt_required_properties(struct node *stmtnp)
{
        struct lut *lutp = stmtnp->u.stmt.lutp;
        struct node *np = stmtnp->u.stmt.np;
        int i;

        for (i = 0; Allowednames[i].t; i++)
                if (stmtnp->t == Allowednames[i].t &&
                    Allowednames[i].required &&
                    tree_s2np_lut_lookup(lutp, Allowednames[i].name) == NULL)
                        outfl(Allowednames[i].outflags,
                            np->file, np->line,
                            "%s statement missing property: %s",
                            ptree_nodetype2str(stmtnp->t),
                            Allowednames[i].name);
}

void
check_stmt_allowed_properties(enum nodetype t,
    struct node *nvpairnp, struct lut *lutp)
{
        int i;
        const char *s = nvpairnp->u.expr.left->u.name.s;
        struct node *np;

        for (i = 0; Allowednames[i].t; i++)
                if (t == Allowednames[i].t && Allowednames[i].name == NULL) {
                        /* NULL name means just call checker */
                        (*Allowednames[i].checker)(t, s,
                            nvpairnp->u.expr.right);
                        return;
                } else if (t == Allowednames[i].t && s == Allowednames[i].name)
                        break;
        if (Allowednames[i].name == NULL)
                outfl(O_ERR, nvpairnp->file, nvpairnp->line,
                    "illegal property name in %s declaration: %s",
                    ptree_nodetype2str(t), s);
        else if ((np = tree_s2np_lut_lookup(lutp, s)) != NULL) {
                /*
                 * redeclaring prop is allowed if value is the same
                 */
                if (np->t != nvpairnp->u.expr.right->t)
                        outfl(O_ERR, nvpairnp->file, nvpairnp->line,
                            "property redeclared (with differnt type) "
                            "in %s declaration: %s",
                            ptree_nodetype2str(t), s);
                switch (np->t) {
                        case T_NUM:
                        case T_TIMEVAL:
                                if (np->u.ull == nvpairnp->u.expr.right->u.ull)
                                        return;
                                break;

                        case T_NAME:
                                if (tree_namecmp(np,
                                    nvpairnp->u.expr.right) == 0)
                                        return;
                                break;

                        case T_EVENT:
                                if (tree_eventcmp(np,
                                    nvpairnp->u.expr.right) == 0)
                                        return;
                                break;

                        default:
                                outfl(O_ERR, nvpairnp->file, nvpairnp->line,
                                    "value for property \"%s\" is an "
                                    "invalid type: %s",
                                    nvpairnp->u.expr.left->u.name.s,
                                    ptree_nodetype2str(np->t));
                                return;
                }
                outfl(O_ERR, nvpairnp->file, nvpairnp->line,
                    "property redeclared in %s declaration: %s",
                    ptree_nodetype2str(t), s);
        } else
                (*Allowednames[i].checker)(t, s, nvpairnp->u.expr.right);
}

void
check_propnames(enum nodetype t, struct node *np, int from, int to)
{
        struct node *dnp;
        struct lut *lutp;

        ASSERT(np != NULL);
        ASSERTinfo(np->t == T_EVENT || np->t == T_LIST || np->t == T_ARROW,
            ptree_nodetype2str(np->t));

        if (np->t == T_EVENT) {
                switch (np->u.event.ename->u.name.t) {
                case N_UNSPEC:
                        outfl(O_ERR, np->file, np->line,
                            "name in %s statement must begin with "
                            "type (example: \"error.\")",
                            ptree_nodetype2str(t));
                        return;
                case N_FAULT:
                        lutp = Faults;
                        if (to) {
                                outfl(O_ERR, np->file, np->line,
                                    "%s has fault on right side of \"->\"",
                                    ptree_nodetype2str(t));
                                return;
                        }
                        if (!from) {
                                outfl(O_DIE, np->file, np->line,
                                    "internal error: %s has fault without "
                                    "from flag",
                                    ptree_nodetype2str(t));
                        }
                        break;
                case N_UPSET:
                        lutp = Upsets;
                        if (to) {
                                outfl(O_ERR, np->file, np->line,
                                    "%s has upset on right side of \"->\"",
                                    ptree_nodetype2str(t));
                                return;
                        }
                        if (!from)
                                outfl(O_DIE, np->file, np->line,
                                    "internal error: %s has upset without "
                                    "from flag",
                                    ptree_nodetype2str(t));
                        break;
                case N_DEFECT:
                        lutp = Defects;
                        if (to) {
                                outfl(O_ERR, np->file, np->line,
                                    "%s has defect on right side of \"->\"",
                                    ptree_nodetype2str(t));
                                return;
                        }
                        if (!from) {
                                outfl(O_DIE, np->file, np->line,
                                    "internal error: %s has defect without "
                                    "from flag",
                                    ptree_nodetype2str(t));
                        }
                        break;
                case N_ERROR:
                        lutp = Errors;
                        if (!from && !to)
                                outfl(O_DIE, np->file, np->line,
                                    "%s has error without from or to flags",
                                    ptree_nodetype2str(t));
                        break;
                case N_EREPORT:
                        lutp = Ereports;
                        if (from) {
                                outfl(O_ERR, np->file, np->line,
                                    "%s has report on left side of \"->\"",
                                    ptree_nodetype2str(t));
                                return;
                        }
                        if (!to)
                                outfl(O_DIE, np->file, np->line,
                                    "internal error: %s has report without "
                                    "to flag",
                                    ptree_nodetype2str(t));
                        break;
                default:
                        outfl(O_DIE, np->file, np->line,
                            "internal error: check_propnames: "
                            "unexpected type: %d", np->u.name.t);
                }

                if ((dnp = tree_event2np_lut_lookup(lutp, np)) == NULL) {
                        outfl(O_ERR, np->file, np->line,
                            "%s statement contains undeclared event",
                            ptree_nodetype2str(t));
                } else
                        dnp->u.stmt.flags |= STMT_REF;
                np->u.event.declp = dnp;
        } else if (np->t == T_LIST) {
                check_propnames(t, np->u.expr.left, from, to);
                check_propnames(t, np->u.expr.right, from, to);
        } else if (np->t == T_ARROW) {
                check_propnames(t, np->u.arrow.lhs, 1, to);
                check_propnames(t, np->u.arrow.rhs, from, 1);
        }
}

static struct lut *
record_iterators(struct node *np, struct lut *ex)
{
        if (np == NULL)
                return (ex);

        switch (np->t) {
        case T_ARROW:
                ex = record_iterators(np->u.arrow.lhs, ex);
                ex = record_iterators(np->u.arrow.rhs, ex);
                break;

        case T_LIST:
                ex = record_iterators(np->u.expr.left, ex);
                ex = record_iterators(np->u.expr.right, ex);
                break;

        case T_EVENT:
                ex = record_iterators(np->u.event.epname, ex);
                break;

        case T_NAME:
                if (np->u.name.child && np->u.name.child->t == T_NAME)
                        ex = lut_add(ex, (void *) np->u.name.child->u.name.s,
                            (void *) np, NULL);
                ex = record_iterators(np->u.name.next, ex);
                break;

        default:
                outfl(O_DIE, np->file, np->line,
                    "record_iterators: internal error: unexpected type: %s",
                    ptree_nodetype2str(np->t));
        }

        return (ex);
}

void
check_exprscope(struct node *np, struct lut *ex)
{
        if (np == NULL)
                return;

        switch (np->t) {
        case T_EVENT:
                check_exprscope(np->u.event.eexprlist, ex);
                break;

        case T_ARROW:
                check_exprscope(np->u.arrow.lhs, ex);
                check_exprscope(np->u.arrow.rhs, ex);
                break;

        case T_NAME:
                if (np->u.name.child && np->u.name.child->t == T_NAME) {
                        if (lut_lookup(ex,
                            (void *) np->u.name.child->u.name.s, NULL) == NULL)
                                outfl(O_ERR, np->file, np->line,
                                    "constraint contains undefined"
                                    " iterator: %s",
                                    np->u.name.child->u.name.s);
                }
                check_exprscope(np->u.name.next, ex);
                break;

        case T_QUOTE:
        case T_GLOBID:
                break;

        case T_ASSIGN:
        case T_NE:
        case T_EQ:
        case T_LIST:
        case T_AND:
        case T_OR:
        case T_NOT:
        case T_ADD:
        case T_SUB:
        case T_MUL:
        case T_DIV:
        case T_MOD:
        case T_LT:
        case T_LE:
        case T_GT:
        case T_GE:
        case T_BITAND:
        case T_BITOR:
        case T_BITXOR:
        case T_BITNOT:
        case T_LSHIFT:
        case T_RSHIFT:
        case T_CONDIF:
        case T_CONDELSE:
                check_exprscope(np->u.expr.left, ex);
                check_exprscope(np->u.expr.right, ex);
                break;

        case T_FUNC:
                check_exprscope(np->u.func.arglist, ex);
                break;

        case T_NUM:
        case T_TIMEVAL:
                break;

        default:
                outfl(O_DIE, np->file, np->line,
                    "check_exprscope: internal error: unexpected type: %s",
                    ptree_nodetype2str(np->t));
        }
}

/*
 * check_propscope -- check constraints for out of scope variable refs
 */
void
check_propscope(struct node *np)
{
        struct lut *ex;

        ex = record_iterators(np, NULL);
        check_exprscope(np, ex);
        lut_free(ex, NULL, NULL);
}

/*
 * check_upset_engine -- validate the engine property in an upset statement
 *
 * we do this after the full parse tree has been constructed rather than while
 * building the parse tree because it is inconvenient for the user if we
 * require SERD engines to be declared before used in an upset "engine"
 * property.
 */

/*ARGSUSED*/
void
check_upset_engine(struct node *lhs, struct node *rhs, void *arg)
{
        enum nodetype t = (enum nodetype)arg;
        struct node *engnp;
        struct node *declp;

        ASSERTeq(rhs->t, t, ptree_nodetype2str);

        if ((engnp = tree_s2np_lut_lookup(rhs->u.stmt.lutp, L_engine)) == NULL)
                return;

        ASSERT(engnp->t == T_EVENT);

        if ((declp = tree_event2np_lut_lookup(SERDs, engnp)) == NULL) {
                outfl(O_ERR, engnp->file, engnp->line,
                    "%s %s property contains undeclared name",
                    ptree_nodetype2str(t), L_engine);
                return;
        }
        engnp->u.event.declp = declp;
}

/*
 * check_refcount -- see if declared names are used
 *
 * this is run after the entire parse tree is constructed, so a refcount
 * of zero means the name has been declared but otherwise not used.
 */

void
check_refcount(struct node *lhs, struct node *rhs, void *arg)
{
        enum nodetype t = (enum nodetype)arg;

        ASSERTeq(rhs->t, t, ptree_nodetype2str);

        if (rhs->u.stmt.flags & STMT_REF)
                return;

        outfl(O_WARN|O_NONL, rhs->file, rhs->line,
            "%s name declared but not used: ", ptree_nodetype2str(t));
        ptree_name(O_WARN|O_NONL, lhs);
        out(O_WARN, NULL);
}

/*
 * set check_cycle_warninglevel only for val >= 0
 */
int
check_cycle_level(long long val)
{
        static int check_cycle_warninglevel = -1;

        if (val == 0)
                check_cycle_warninglevel = 0;
        else if (val > 0)
                check_cycle_warninglevel = 1;

        return (check_cycle_warninglevel);
}

/*
 * check_cycle -- see props from an error have cycles
 *
 * this is run after the entire parse tree is constructed, for
 * each error that has been declared.
 */

/*ARGSUSED*/
void
check_cycle(struct node *lhs, struct node *rhs, void *arg)
{
        struct node *np;

        ASSERTeq(rhs->t, T_ERROR, ptree_nodetype2str);

        if (rhs->u.stmt.flags & STMT_CYCLE)
                return;         /* already reported this cycle */

        if (rhs->u.stmt.flags & STMT_CYMARK) {
#ifdef ESC
                int warninglevel;

                warninglevel = check_cycle_level(-1);
                if (warninglevel <= 0) {
                        int olevel = O_ERR;

                        if (warninglevel == 0)
                                olevel = O_WARN;

                        out(olevel|O_NONL, "cycle in propagation tree: ");
                        ptree_name(olevel|O_NONL, rhs->u.stmt.np);
                        out(olevel, NULL);
                }
#endif /* ESC */

                rhs->u.stmt.flags |= STMT_CYCLE;
        }

        rhs->u.stmt.flags |= STMT_CYMARK;

        /* for each propagation */
        for (np = Props; np; np = np->u.stmt.next)
                check_cycle_lhs(rhs, np->u.stmt.np);

        rhs->u.stmt.flags &= ~STMT_CYMARK;
}

/*
 * check_cycle_lhs -- find the lhs of an arrow for cycle checking
 */

static void
check_cycle_lhs(struct node *stmtnp, struct node *arrow)
{
        struct node *trylhs;
        struct node *tryrhs;

        /* handle cascaded arrows */
        switch (arrow->u.arrow.lhs->t) {
        case T_ARROW:
                /* first recurse left */
                check_cycle_lhs(stmtnp, arrow->u.arrow.lhs);

                /*
                 * return if there's a list of events internal to
                 * cascaded props (which is not allowed)
                 */
                if (arrow->u.arrow.lhs->u.arrow.rhs->t != T_EVENT)
                        return;

                /* then try this arrow (thing cascaded *to*) */
                trylhs = arrow->u.arrow.lhs->u.arrow.rhs;
                tryrhs = arrow->u.arrow.rhs;
                break;

        case T_EVENT:
        case T_LIST:
                trylhs = arrow->u.arrow.lhs;
                tryrhs = arrow->u.arrow.rhs;
                break;

        default:
                out(O_DIE, "lhs: unexpected type: %s",
                    ptree_nodetype2str(arrow->u.arrow.lhs->t));
                /*NOTREACHED*/
        }

        check_cycle_lhs_try(stmtnp, trylhs, tryrhs);
}

/*
 * check_cycle_lhs_try -- try matching an event name on lhs of an arrow
 */

static void
check_cycle_lhs_try(struct node *stmtnp, struct node *lhs, struct node *rhs)
{
        if (lhs->t == T_LIST) {
                check_cycle_lhs_try(stmtnp, lhs->u.expr.left, rhs);
                check_cycle_lhs_try(stmtnp, lhs->u.expr.right, rhs);
                return;
        }

        ASSERT(lhs->t == T_EVENT);

        if (tree_eventcmp(stmtnp->u.stmt.np, lhs) != 0)
                return;         /* no match */

        check_cycle_rhs(rhs);
}

/*
 * check_cycle_rhs -- foreach error on rhs, see if we cycle to a marked error
 */

static void
check_cycle_rhs(struct node *rhs)
{
        struct node *dnp;

        if (rhs->t == T_LIST) {
                check_cycle_rhs(rhs->u.expr.left);
                check_cycle_rhs(rhs->u.expr.right);
                return;
        }

        ASSERT(rhs->t == T_EVENT);

        if (rhs->u.event.ename->u.name.t != N_ERROR)
                return;

        if ((dnp = tree_event2np_lut_lookup(Errors, rhs)) == NULL) {
                outfl(O_ERR|O_NONL,
                    rhs->file, rhs->line,
                    "unexpected undeclared event during cycle check");
                ptree_name(O_ERR|O_NONL, rhs);
                out(O_ERR, NULL);
                return;
        }
        check_cycle(NULL, dnp, 0);
}

/*
 * Force iterators to be simple names, expressions, or numbers
 */
void
check_name_iterator(struct node *np)
{
        if (np->u.name.child->t != T_NUM &&
            np->u.name.child->t != T_NAME &&
            np->u.name.child->t != T_CONDIF &&
            np->u.name.child->t != T_SUB &&
            np->u.name.child->t != T_ADD &&
            np->u.name.child->t != T_MUL &&
            np->u.name.child->t != T_DIV &&
            np->u.name.child->t != T_MOD &&
            np->u.name.child->t != T_LSHIFT &&
            np->u.name.child->t != T_RSHIFT) {
                outfl(O_ERR|O_NONL, np->file, np->line,
                "invalid iterator: ");
                ptree_name_iter(O_ERR|O_NONL, np);
                out(O_ERR, NULL);
        }
}

/*
 * Iterators on a declaration may only be implicit
 */
void
check_type_iterator(struct node *np)
{
        while (np != NULL) {
                if (np->t == T_EVENT) {
                        np = np->u.event.epname;
                } else if (np->t == T_NAME) {
                        if (np->u.name.child != NULL &&
                            np->u.name.child->t != T_NUM) {
                                outfl(O_ERR|O_NONL, np->file, np->line,
                                    "explicit iterators disallowed "
                                    "in declarations: ");
                                ptree_name_iter(O_ERR|O_NONL, np);
                                out(O_ERR, NULL);
                        }
                        np = np->u.name.next;
                } else {
                        break;
                }
        }
}

void
check_cat_list(struct node *np)
{
        if (np->t == T_FUNC)
                check_func(np);
        else if (np->t == T_LIST) {
                check_cat_list(np->u.expr.left);
                check_cat_list(np->u.expr.right);
        }
}

void
check_func(struct node *np)
{
        struct node *arglist = np->u.func.arglist;

        ASSERTinfo(np->t == T_FUNC, ptree_nodetype2str(np->t));

        if (np->u.func.s == L_within) {
                switch (arglist->t) {
                case T_NUM:
                        if (arglist->u.ull != 0ULL) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "parameter of within must be 0"
                                    ", \"infinity\" or a time value.");
                        }
                        break;

                case T_NAME:
                        if (arglist->u.name.s != L_infinity) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "parameter of within must be 0"
                                    ", \"infinity\" or a time value.");
                        }
                        break;

                case T_LIST:
                        /*
                         * if two parameters, the left or min must be
                         * either T_NUM or T_TIMEVAL
                         */
                        if (arglist->u.expr.left->t != T_NUM &&
                            arglist->u.expr.left->t != T_TIMEVAL) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "first parameter of within must be"
                                    " either a time value or zero.");
                        }

                        /*
                         * if two parameters, the right or max must
                         * be either T_NUM, T_NAME or T_TIMEVAL
                         */
                        if (arglist->u.expr.right->t != T_NUM &&
                            arglist->u.expr.right->t != T_TIMEVAL &&
                            arglist->u.expr.right->t != T_NAME) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "second parameter of within must "
                                    "be 0, \"infinity\" or time value.");
                        }

                        /*
                         * if right or left is a T_NUM it must
                         * be zero
                         */
                        if ((arglist->u.expr.left->t == T_NUM) &&
                            (arglist->u.expr.left->u.ull != 0ULL)) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "within parameter must be "
                                    "0 or a time value.");
                        }
                        if ((arglist->u.expr.right->t == T_NUM) &&
                            (arglist->u.expr.right->u.ull != 0ULL)) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "within parameter must be "
                                    "0 or a time value.");
                        }

                        /* if right is a T_NAME it must be "infinity" */
                        if ((arglist->u.expr.right->t == T_NAME) &&
                            (arglist->u.expr.right->u.name.s != L_infinity)) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "\"infinity\" is the only "
                                    "valid name for within parameter.");
                        }

                        /*
                         * the first parameter [min] must not be greater
                         * than the second parameter [max].
                         */
                        if (arglist->u.expr.left->u.ull >
                            arglist->u.expr.right->u.ull) {
                                outfl(O_ERR, arglist->file, arglist->line,
                                    "the first value (min) of"
                                    " within must be less than"
                                    " the second (max) value");
                        }
                        break;

                case T_TIMEVAL:
                        break; /* no restrictions on T_TIMEVAL */

                default:
                        outfl(O_ERR, arglist->file, arglist->line,
                            "parameter of within must be 0"
                            ", \"infinity\" or a time value.");
                }
        } else if (np->u.func.s == L_call) {
                if (arglist->t != T_QUOTE &&
                    arglist->t != T_LIST &&
                    arglist->t != T_GLOBID &&
                    arglist->t != T_CONDIF &&
                    arglist->t != T_LIST &&
                    arglist->t != T_FUNC)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "invalid first argument to call()");
        } else if (np->u.func.s == L_fru) {
                if (arglist->t != T_NAME)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to fru() must be a path");
        } else if (np->u.func.s == L_asru) {
                if (arglist->t != T_NAME)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to asru() must be a path");
        } else if (np->u.func.s == L_is_connected ||
            np->u.func.s == L_is_under) {
                if (arglist->t == T_LIST &&
                    (arglist->u.expr.left->t == T_NAME ||
                    arglist->u.expr.left->t == T_FUNC) &&
                    (arglist->u.expr.right->t == T_NAME ||
                    arglist->u.expr.right->t == T_FUNC)) {
                        if (arglist->u.expr.left->t == T_FUNC)
                                check_func(arglist->u.expr.left);
                        if (arglist->u.expr.right->t == T_FUNC)
                                check_func(arglist->u.expr.right);
                } else {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "%s() must have paths or calls to "
                            "fru() and/or asru() as arguments",
                            np->u.func.s);
                }
        } else if (np->u.func.s == L_is_on) {
                if (arglist->t == T_NAME || arglist->t == T_FUNC) {
                        if (arglist->t == T_FUNC)
                                check_func(arglist);
                } else {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to is_on() must be a path or a call to "
                            "fru() or asru()");
                }
        } else if (np->u.func.s == L_is_present) {
                if (arglist->t == T_NAME || arglist->t == T_FUNC) {
                        if (arglist->t == T_FUNC)
                                check_func(arglist);
                } else {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to is_present() must be a path or a call "
                            "to fru() or asru()");
                }
        } else if (np->u.func.s == L_has_fault) {
                if (arglist->t == T_LIST &&
                    (arglist->u.expr.left->t == T_NAME ||
                    arglist->u.expr.left->t == T_FUNC) &&
                    arglist->u.expr.right->t == T_QUOTE) {
                        if (arglist->u.expr.left->t == T_FUNC)
                                check_func(arglist->u.expr.left);
                } else {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "%s() must have path or call to "
                            "fru() and/or asru() as first argument; "
                            "second argument must be a string", np->u.func.s);
                }
        } else if (np->u.func.s == L_is_type) {
                if (arglist->t == T_NAME || arglist->t == T_FUNC) {
                        if (arglist->t == T_FUNC)
                                check_func(arglist);
                } else {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to is_type() must be a path or a call to "
                            "fru() or asru()");
                }
        } else if (np->u.func.s == L_confcall) {
                if (arglist->t != T_QUOTE &&
                    (arglist->t != T_LIST ||
                    arglist->u.expr.left->t != T_QUOTE))
                        outfl(O_ERR, arglist->file, arglist->line,
                            "confcall(): first argument must be a string "
                            "(the name of the operation)");
        } else if (np->u.func.s == L_confprop ||
            np->u.func.s == L_confprop_defined) {
                if (arglist->t == T_LIST &&
                    (arglist->u.expr.left->t == T_NAME ||
                    arglist->u.expr.left->t == T_FUNC) &&
                    arglist->u.expr.right->t == T_QUOTE) {
                        if (arglist->u.expr.left->t == T_FUNC)
                                check_func(arglist->u.expr.left);
                } else {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "%s(): first argument must be a path or a call to "
                            "fru() or asru(); "
                            "second argument must be a string", np->u.func.s);
                }
        } else if (np->u.func.s == L_count) {
                if (arglist->t != T_EVENT) {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "count(): argument must be an engine name");
                }
        } else if (np->u.func.s == L_defined) {
                if (arglist->t != T_GLOBID)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to defined() must be a global");
        } else if (np->u.func.s == L_payloadprop) {
                if (arglist->t != T_QUOTE)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to payloadprop() must be a string");
        } else if (np->u.func.s == L_payloadprop_contains) {
                if (arglist->t != T_LIST ||
                    arglist->u.expr.left->t != T_QUOTE ||
                    arglist->u.expr.right == NULL)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "args to payloadprop_contains(): must be a quoted "
                            "string (property name) and an expression "
                            "(to match)");
        } else if (np->u.func.s == L_payloadprop_defined) {
                if (arglist->t != T_QUOTE)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "arg to payloadprop_defined(): must be a quoted "
                            "string");
        } else if (np->u.func.s == L_setpayloadprop) {
                if (arglist->t == T_LIST &&
                    arglist->u.expr.left->t == T_QUOTE) {
                        if (arglist->u.expr.right->t == T_FUNC)
                                check_func(arglist->u.expr.right);
                } else {
                        outfl(O_ERR, arglist->file, arglist->line,
                            "setpayloadprop(): "
                            "first arg must be a string, "
                            "second arg a value");
                }
        } else if (np->u.func.s == L_setserdn || np->u.func.s == L_setserdt ||
            np->u.func.s == L_setserdsuffix || np->u.func.s ==
            L_setserdincrement) {
                if (arglist->t == T_FUNC)
                        check_func(arglist);
        } else if (np->u.func.s == L_cat) {
                check_cat_list(arglist);
        } else if (np->u.func.s == L_envprop) {
                if (arglist->t != T_QUOTE)
                        outfl(O_ERR, arglist->file, arglist->line,
                            "argument to envprop() must be a string");
        } else
                outfl(O_WARN, np->file, np->line,
                    "possible platform-specific function: %s",
                    np->u.func.s);
}

void
check_expr(struct node *np)
{
        ASSERT(np != NULL);

        switch (np->t) {
        case T_ASSIGN:
                ASSERT(np->u.expr.left != NULL);
                if (np->u.expr.left->t != T_GLOBID)
                        outfl(O_ERR, np->file, np->line,
                            "assignment only allowed to globals (e.g. $a)");
                break;
        }
}

void
check_event(struct node *np)
{
        ASSERT(np != NULL);
        ASSERTinfo(np->t == T_EVENT, ptree_nodetype2str(np->t));

        if (np->u.event.epname == NULL) {
                outfl(O_ERR|O_NONL, np->file, np->line,
                    "pathless events not allowed: ");
                ptree_name(O_ERR|O_NONL, np->u.event.ename);
                out(O_ERR, NULL);
        }
}

/*
 * check for properties that are required on declarations. This
 * should be done after all declarations since they can be
 * redeclared with a different set of properties.
 */
/*ARGSUSED*/
void
check_required_props(struct node *lhs, struct node *rhs, void *arg)
{
        ASSERTeq(rhs->t, (enum nodetype)arg, ptree_nodetype2str);

        check_stmt_required_properties(rhs);
}

/*
 * check that cascading prop statements do not contain lists internally.
 * the first and last event lists in the cascading prop may be single
 * events or lists of events.
 */
/*ARGSUSED*/
void
check_proplists(enum nodetype t, struct node *np)
{
        ASSERT(np->t == T_ARROW);
        /*
         * not checking the right hand side of the top level prop
         * since it is the last part of the propagation and can be
         * an event or list of events
         */
        check_proplists_lhs(t, np->u.arrow.lhs);
}

/*ARGSUSED*/
static void
check_proplists_lhs(enum nodetype t, struct node *lhs)
{
        if (lhs->t == T_ARROW) {
                if (lhs->u.arrow.rhs->t == T_LIST) {
                        outfl(O_ERR, lhs->file, lhs->line,
                            "lists are not allowed internally on cascading %s",
                            (t == T_PROP) ? "propagations" : "masks");
                }
                check_proplists_lhs(t, lhs->u.arrow.lhs);
        }
}