/*
* 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
* 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 (c) 2013, Joyent Inc. All rights reserved.
* Copyright (c) 2012, 2016 by Delphix. All rights reserved.
*/
/*
* DTrace D Language Parser
*
* parsing grammar dt_grammar.y, and this file, dt_parser.c, which handles
* the construction of the parse tree nodes and their syntactic validation.
* The parse tree is constructed of dt_node_t structures (see <dt_parser.h>)
* that are built in two passes: (1) the "create" pass, where the parse tree
* nodes are allocated by calls from the grammar to dt_node_*() subroutines,
* and (2) the "cook" pass, where nodes are coalesced, assigned D types, and
* validated according to the syntactic rules of the language.
*
* All node allocations are performed using dt_node_alloc(). All node frees
* during the parsing phase are performed by dt_node_free(), which frees node-
* internal state but does not actually free the nodes. All final node frees
* are done as part of the end of dt_compile() or as part of destroying
* persistent identifiers or translators which have embedded nodes.
*
* The dt_node_* routines that implement pass (1) may allocate new nodes. The
* dt_cook_* routines that implement pass (2) may *not* allocate new nodes.
* They may free existing nodes using dt_node_free(), but they may not actually
* deallocate any dt_node_t's. Currently dt_cook_op2() is an exception to this
* rule: see the comments therein for how this issue is resolved.
*
* The dt_cook_* routines are responsible for (at minimum) setting the final
* are set manually (i.e. not by one of the type assignment functions), then
* the DT_NF_COOKED flag must be set manually on the node.
*
* The cooking pass can be applied to the same parse tree more than once (used
* in the case of a comma-separated list of probe descriptions). As such, the
* cook routines must not perform any parse tree transformations which would
* be invalid if the tree were subsequently cooked using a different context.
*
* The dn_ctfp and dn_type fields form the type of the node. This tuple can
* take on the following set of values, which form our type invariants:
*
* 1. dn_ctfp = NULL, dn_type = CTF_ERR
*
* In this state, the node has unknown type and is not yet cooked. The
* DT_NF_COOKED flag is not yet set on the node.
*
* 2. dn_ctfp = DT_DYN_CTFP(dtp), dn_type = DT_DYN_TYPE(dtp)
*
* In this state, the node is a dynamic D type. This means that generic
* operations are not valid on this node and only code that knows how to
* examine the inner details of the node can operate on it. A <DYN> node
* must have dn_ident set to point to an identifier describing the object
* and its type. The DT_NF_REF flag is set for all nodes of type <DYN>.
* At present, the D compiler uses the <DYN> type for:
*
* - associative arrays that do not yet have a value type defined
* - translated data (i.e. the result of the xlate operator)
* - aggregations
*
* 3. dn_ctfp = DT_STR_CTFP(dtp), dn_type = DT_STR_TYPE(dtp)
*
* In this state, the node is of type D string. The string type is really
* a char[0] typedef, but requires special handling throughout the compiler.
*
* 4. dn_ctfp != NULL, dn_type = any other type ID
*
* APIs can be used to learn more about the type name or structure. When
* the type is assigned, the DT_NF_SIGNED, DT_NF_REF, and DT_NF_BITFIELD
* flags cache the corresponding attributes of the underlying CTF type.
*/
#include <sys/sysmacros.h>
#include <limits.h>
#include <setjmp.h>
#include <strings.h>
#include <assert.h>
#include <alloca.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <dt_impl.h>
#include <dt_grammar.h>
#include <dt_module.h>
#include <dt_provider.h>
#include <dt_string.h>
#include <dt_as.h>
static const char *
{
switch (op) {
case DT_TOK_COMMA: return (",");
case DT_TOK_ELLIPSIS: return ("...");
case DT_TOK_ASGN: return ("=");
case DT_TOK_ADD_EQ: return ("+=");
case DT_TOK_SUB_EQ: return ("-=");
case DT_TOK_MUL_EQ: return ("*=");
case DT_TOK_DIV_EQ: return ("/=");
case DT_TOK_MOD_EQ: return ("%=");
case DT_TOK_AND_EQ: return ("&=");
case DT_TOK_XOR_EQ: return ("^=");
case DT_TOK_OR_EQ: return ("|=");
case DT_TOK_LSH_EQ: return ("<<=");
case DT_TOK_RSH_EQ: return (">>=");
case DT_TOK_QUESTION: return ("?");
case DT_TOK_COLON: return (":");
case DT_TOK_LOR: return ("||");
case DT_TOK_LXOR: return ("^^");
case DT_TOK_LAND: return ("&&");
case DT_TOK_BOR: return ("|");
case DT_TOK_XOR: return ("^");
case DT_TOK_BAND: return ("&");
case DT_TOK_EQU: return ("==");
case DT_TOK_NEQ: return ("!=");
case DT_TOK_LT: return ("<");
case DT_TOK_LE: return ("<=");
case DT_TOK_GT: return (">");
case DT_TOK_GE: return (">=");
case DT_TOK_LSH: return ("<<");
case DT_TOK_RSH: return (">>");
case DT_TOK_ADD: return ("+");
case DT_TOK_SUB: return ("-");
case DT_TOK_MUL: return ("*");
case DT_TOK_DIV: return ("/");
case DT_TOK_MOD: return ("%");
case DT_TOK_LNEG: return ("!");
case DT_TOK_BNEG: return ("~");
case DT_TOK_ADDADD: return ("++");
case DT_TOK_PREINC: return ("++");
case DT_TOK_POSTINC: return ("++");
case DT_TOK_SUBSUB: return ("--");
case DT_TOK_PREDEC: return ("--");
case DT_TOK_POSTDEC: return ("--");
case DT_TOK_IPOS: return ("+");
case DT_TOK_INEG: return ("-");
case DT_TOK_DEREF: return ("*");
case DT_TOK_ADDROF: return ("&");
case DT_TOK_OFFSETOF: return ("offsetof");
case DT_TOK_SIZEOF: return ("sizeof");
case DT_TOK_STRINGOF: return ("stringof");
case DT_TOK_XLATE: return ("xlate");
case DT_TOK_LPAR: return ("(");
case DT_TOK_RPAR: return (")");
case DT_TOK_LBRAC: return ("[");
case DT_TOK_RBRAC: return ("]");
case DT_TOK_PTR: return ("->");
case DT_TOK_DOT: return (".");
case DT_TOK_STRING: return ("<string>");
case DT_TOK_IDENT: return ("<ident>");
case DT_TOK_TNAME: return ("<type>");
case DT_TOK_INT: return ("<int>");
default: return ("<?>");
}
}
int
{
while (isspace(*p))
p++; /* skip leading whitespace prior to token */
break; /* empty string or single token remaining */
if (*q == '`') {
/*
* Copy from the start of the token (p) to the location
* backquote (q) to extract the nul-terminated object.
*/
/*
* Copy the original string up to the start of this
* token (p) into type, and then concatenate everything
* after q. This is the type name without the object.
*/
/*
* There may be at most three delimeters. The second
* delimeter is usually used to distinguish the type
* within a given module, however, there could be a link
* map id on the scene in which case that delimeter
* would be the third. We determine presence of the lmid
* if it rouglhly meets the from LM[0-9]
*/
return (dt_set_errno(dtp,
EDT_BADSCOPE));
if (q[1] != 'L' || q[2] != 'M')
return (dt_set_errno(dtp,
EDT_BADSCOPE));
}
}
}
if (yypcb->pcb_idepth != 0)
else
}
/*
* When we parse type expressions or parse an expression with unary "&", we
* need to find a type that is a pointer to a previously known type.
* Unfortunately CTF is limited to a per-container view, so ctf_type_pointer()
* alone does not suffice for our needs. We provide a more intelligent wrapper
* for the compiler that attempts to compute a pointer to either the given type
* or its base (that is, we try both "foo_t *" and "struct foo *"), and also
* to potentially construct the required type on-the-fly.
*/
int
{
return (0);
}
if (yypcb->pcb_idepth != 0)
else
}
}
return (0);
}
const char *
{
return (buf);
}
/*
* Perform the "usual arithmetic conversions" to determine which of the two
* input operand types should be promoted and used as a result type. The
* rules for this are described in ISOC[6.3.1.8] and K&R[A6.5].
*/
static void
{
if (lkind == CTF_K_ENUM) {
}
if (rkind == CTF_K_ENUM) {
}
}
}
/*
* Compute an integer rank based on the size and unsigned status.
* If rank is identical, pick the "larger" of the equivalent types
* which we define as having a larger base ctf_id_t. If rank is
* different, pick the type with the greater rank.
*/
goto return_rtype;
else
goto return_ltype;
goto return_ltype;
} else
goto return_rtype;
return;
}
void
{
}
const char *
{
char *s;
case DT_NODE_INT:
break;
case DT_NODE_STRING:
free(s);
break;
case DT_NODE_IDENT:
break;
case DT_NODE_VAR:
case DT_NODE_FUNC:
case DT_NODE_AGG:
case DT_NODE_INLINE:
case DT_IDENT_FUNC:
case DT_IDENT_AGGFUNC:
case DT_IDENT_ACTFUNC:
suffix = "( )";
break;
case DT_IDENT_AGG:
prefix = "@";
break;
}
break;
case DT_NODE_SYM:
break;
case DT_NODE_TYPE:
break;
case DT_NODE_OP1:
case DT_NODE_OP2:
case DT_NODE_OP3:
break;
case DT_NODE_DEXPR:
case DT_NODE_DFUNC:
break;
case DT_NODE_PDESC:
"probe description %s:%s:%s:%s",
} else {
}
break;
case DT_NODE_CLAUSE:
break;
case DT_NODE_MEMBER:
break;
case DT_NODE_XLATOR:
break;
case DT_NODE_PROG:
break;
default:
break;
}
return (buf);
}
/*
* dt_node_xalloc() can be used to create new parse nodes from any libdtrace
* caller. The caller is responsible for assigning dn_link appropriately.
*/
{
return (NULL);
return (dnp);
}
/*
* dt_node_alloc() is used to create new parse nodes from the parser. It
* assigns the node location based on the current lexer line number and places
* the new node on the default allocation list. If allocation fails, we
* automatically longjmp the caller back to the enclosing compilation call.
*/
static dt_node_t *
{
return (dnp);
}
void
{
switch (kind) {
case DT_NODE_STRING:
case DT_NODE_IDENT:
case DT_NODE_TYPE:
break;
case DT_NODE_VAR:
case DT_NODE_FUNC:
case DT_NODE_PROBE:
}
break;
case DT_NODE_OP1:
}
break;
case DT_NODE_OP3:
}
/*FALLTHRU*/
case DT_NODE_OP2:
}
}
break;
case DT_NODE_DEXPR:
case DT_NODE_DFUNC:
}
break;
case DT_NODE_AGG:
}
break;
case DT_NODE_PDESC:
break;
case DT_NODE_CLAUSE:
break;
case DT_NODE_MEMBER:
}
break;
case DT_NODE_PROVIDER:
break;
case DT_NODE_PROG:
break;
}
}
void
{
char a[DTRACE_ATTR2STR_MAX];
char s[BUFSIZ];
dtrace_attr2str(attr, a, sizeof (a)));
}
}
void
{
if (e.cte_format & CTF_INT_SIGNED)
}
}
kind == CTF_K_FORWARD ||
if (user)
}
void
{
}
const char *
{
return (buf);
}
return (buf);
}
}
{
return (0);
/*
* Here we have a 32-bit user pointer that is being used with a 64-bit
* kernel. When we're using it and its tagged as a userland reference --
* then we need to keep it as a 32-bit pointer. However, if we are
* referring to it as a kernel address, eg. being used after a copyin()
* then we need to make sure that we actually return the kernel's size
* of a pointer, 8 bytes.
*/
return (8);
}
/*
* Determine if the specified parse tree node references an identifier of the
* specified kind, and if so return a pointer to it; otherwise return NULL.
* This function resolves the identifier itself, following through any inlines.
*/
{
case DT_NODE_VAR:
case DT_NODE_SYM:
case DT_NODE_FUNC:
case DT_NODE_AGG:
case DT_NODE_INLINE:
case DT_NODE_PROBE:
}
if (dt_node_is_dynamic(dnp)) {
}
return (NULL);
}
{
/*
* The size of the node as used for the sizeof() operator depends on
* the kind of the node. If the node is a SYM, the size is obtained
* from the symbol table; if it is not a SYM, the size is determined
* from the node's type. This is slightly different from C's sizeof()
* operator in that (for example) when applied to a function, sizeof()
* will evaluate to the length of the function rather than the size of
* the function type.
*/
return (dt_node_type_size(dnp));
return (0);
}
int
{
if (kind == CTF_K_INTEGER &&
return (0); /* void integer */
}
int
{
return (kind == CTF_K_FLOAT &&
e.cte_format == CTF_FP_LDOUBLE));
}
int
{
if (kind == CTF_K_INTEGER &&
return (0); /* void cannot be used as a scalar */
kind == CTF_K_POINTER);
}
int
{
if (kind == CTF_K_INTEGER)
else
return (kind == CTF_K_ENUM);
}
int
{
return (0); /* type is not a pointer */
}
int
{
}
}
int
{
}
int
{
}
int
{
}
int
{
}
int
{
ctf_arinfo_t r;
if (kind == CTF_K_POINTER &&
return (1); /* promote char pointer to string */
return (1); /* promote char array to string */
return (0);
}
int
{
if (dt_node_is_string(dnp))
return (0); /* string are pass-by-ref but act like structs */
}
int
{
if (dt_node_is_dynamic(dnp))
return (0); /* <DYN> is an alias for void but not the same */
if (dt_node_is_stack(dnp))
return (0);
return (0);
}
int
{
ctf_arinfo_t r;
return (0); /* fail if either node is a dynamic variable */
return (0); /* fail if both nodes are integers */
return (0); /* fail if lp is an integer that isn't 0 constant */
return (0); /* fail if rp is an integer that isn't 0 constant */
return (0); /* fail if only one pointer is a userland address */
/*
* Resolve the left-hand and right-hand types to their base type, and
* then resolve the referenced type as well (assuming the base type
* is CTF_K_POINTER or CTF_K_ARRAY). Otherwise [lr]ref = CTF_ERR.
*/
if (!lp_is_int) {
if (lkind == CTF_K_POINTER) {
} else if (lkind == CTF_K_ARRAY &&
}
}
if (!rp_is_int) {
if (rkind == CTF_K_POINTER) {
} else if (rkind == CTF_K_ARRAY &&
}
}
/*
* We know that one or the other type may still be a zero-valued
* integer constant. To simplify the code below, set the integer
* type variables equal to the non-integer types and proceed.
*/
if (lp_is_int) {
} else if (rp_is_int) {
}
/*
* The types are compatible if both are pointers to the same type, or
* if either pointer is a void pointer. If they are compatible, set
* tp to point to the more specific pointer type and return it.
*/
if (compat) {
}
return (compat);
}
/*
* The rules for checking argument types against parameter types are described
* in the ANSI-C spec (see K&R[A7.3.2] and K&R[A7.17]). We use the same rule
* set to determine whether associative array arguments match the prototype.
*/
int
{
return (1); /* integer types are compatible */
return (1); /* string types are compatible */
return (1); /* stack types are compatible */
return (1); /* symaddr types are compatible */
return (1); /* usymaddr types are compatible */
case CTF_K_FUNCTION:
case CTF_K_STRUCT:
case CTF_K_UNION:
default:
}
}
/*
* We provide dt_node_is_posconst() as a convenience routine for callers who
* wish to verify that an argument is a positive non-zero integer constant.
*/
int
{
}
int
{
}
/*
* The original rules for integer constant typing are described in K&R[A2.5.1].
* However, since we support long long, we instead use the rules from ISO C99
* clause 6.4.4.1 since that is where long longs are formally described. The
* rules require us to know whether the constant was specified in decimal or
* in octal or hex, which we do by looking at our lexer's 'yyintdecimal' flag.
* The type of an integer constant is the first of the corresponding list in
* which its value can be represented:
*
* unsuffixed decimal: int, long, long long
* long long, unsigned long long
* suffix [uU]: unsigned int, unsigned long, unsigned long long
* suffix [lL] decimal: long, long long
* suffix [uU][Ll]: unsigned long, unsigned long long
*
* Given that our lexer has already validated the suffixes by regexp matching,
* there is an obvious way to concisely encode these rules: construct an array
* of the types in the order int, unsigned int, long, unsigned long, long long,
* unsigned long long. Compute an integer array starting index based on the
* suffix (e.g. none = 0, u = 1, ull = 5), and compute an increment based on
* index to the end, advancing using the increment, and searching until we
* find a limit that matches or we run out of choices (overflow). To make it
* even faster, we precompute the table of type information in dtrace_open().
*/
{
int i = 0;
const char *p;
char c;
for (p = yyintsuffix; (c = *p) != '\0'; p++) {
if (c == 'U' || c == 'u')
i += 1;
else if (c == 'L' || c == 'l')
i += 2;
}
/*
* If a prefix character is present in macro text, add
* in the corresponding operator node (see dt_lex.l).
*/
switch (yyintprefix) {
case '+':
case '-':
default:
return (dnp);
}
}
}
/*NOTREACHED*/
return (NULL); /* keep gcc happy */
}
{
return (dnp);
}
{
/*
* If the identifier is an inlined integer constant, then create an INT
* node that is a clone of the inline parse tree node and return that
* immediately, allowing this inline to be used in parsing contexts
* that require constant expressions (e.g. scalar array sizes).
*/
return (dnp);
}
}
return (dnp);
}
/*
* Create an empty node of type corresponding to the given declaration.
* Explicit references to user types (C or D) are assigned the default
* stability; references to other types are _dtrace_typattr (Private).
*/
{
int err;
/*
* If 'ddp' is NULL, we get a decl by popping the decl stack. This
* form of dt_node_type() is used by parameter rules in dt_grammar.y.
*/
if (err != 0) {
}
else
return (dnp);
}
/*
* Create a type node corresponding to a varargs (...) parameter by just
* assigning it type CTF_ERR. The decl processing code will handle this.
*/
dt_node_vatype(void)
{
return (dnp);
}
/*
* Instantiate a decl using the contents of the current declaration stack. As
* we do not currently permit decls to be initialized, this function currently
* returns NULL and no parse node is created. When this function is called,
* the topmost scope's ds_ident pointer will be set to NULL (indicating no
* init_declarator rule was matched) or will point to the identifier to use.
*/
dt_node_decl(void)
{
/*
* If we have no declaration identifier, then this is either a spurious
* declaration of an intrinsic type (e.g. "extern int;") or declaration
* or redeclaration of a struct, union, or enum type or tag.
*/
return (NULL);
}
}
/*
* If we are nested inside of a C include file, add the declaration to
* the C definition module; otherwise use the D definition module.
*/
if (yypcb->pcb_idepth != 0)
else
/*
* If we see a global or static declaration of a function prototype,
* treat this as equivalent to a D extern declaration.
*/
switch (class) {
case DT_DC_AUTO:
case DT_DC_REGISTER:
case DT_DC_STATIC:
"appropriate in D\n");
/*NOTREACHED*/
case DT_DC_EXTERN: {
"\t current: %s\n\tprevious: %s\n",
} else {
dt_dprintf("extern %s`%s type=<%s>\n",
}
break;
}
case DT_DC_TYPEDEF:
}
}
/*
* If the source type for the typedef is not defined in the
* target container or its parent, copy the type to the target
* container and reset dtt_ctfp and dtt_type to the copy.
*/
}
}
}
break;
default: {
switch (class) {
case DT_DC_THIS:
break;
case DT_DC_SELF:
break;
default:
idflags = 0;
break;
}
"array declaration requires array dimension or "
}
}
}
/*
* Cache some attributes of the decl to make the rest of this
* code simpler: if the decl is an array which is subscripted
* by a type rather than an integer, then it's an associative
* array (assc). We then expect to match either DT_IDENT_ARRAY
* for associative arrays or DT_IDENT_SCALAR for anything else.
*/
/*
* Create a fake dt_node_t on the stack so we can determine the
* type of any matching identifier by assigning to this node.
* If the pre-existing ident has its di_type set, propagate
* the type by hand so as not to trigger a prototype check for
* arrays (yet); otherwise we use dt_ident_cook() on the ident
* to ensure it is fully initialized before looking at it.
*/
B_FALSE);
if (assc) {
if (class == DT_DC_THIS) {
"may not be declared as local variables:"
}
}
"\t current: %s %s\n\tprevious: %s %s\n",
int argc = 0;
continue; /* tuple length mismatch */
continue;
"identifier redeclared: %s\n"
"\t current: %s, key #%d of type %s\n"
"\tprevious: %s, key #%d of type %s\n",
}
"identifier redeclared: %s\n"
"\t current: %s of %s, tuple length %d\n"
"\tprevious: %s of %s, tuple length %d\n",
}
switch (kind) {
case CTF_K_INTEGER:
}
break;
case CTF_K_STRUCT:
case CTF_K_UNION:
break; /* proceed to declaring */
/*FALLTHRU*/
case CTF_K_FORWARD:
/*NOTREACHED*/
}
"on number of %s variables exceeded\n",
}
dt_dprintf("declare %s %s variable %s, id=%u\n",
/*
* If we are declaring an associative array, use our
* fake parse node to cook the new assoc identifier.
* This will force the ident code to instantiate the
* array type signature corresponding to the list of
* types pointed to by ddp->dd_node. We also reset
* the identifier's attributes based upon the result.
*/
if (assc) {
}
}
}
} /* end of switch */
return (NULL);
}
{
"function designator is not of function type\n");
}
}
}
return (dnp);
}
/*
* The offsetof() function is special because it takes a type name as an
* argument. It does not actually construct its own node; after looking up the
* structure or union offset, we just return an integer node with the offset.
*/
{
char *name;
int err;
free(s);
if (err != 0)
"offsetof operand must be a struct or union type\n");
}
}
"cannot take offset of a bit-field: %s\n", name);
}
}
{
switch (op) {
case DT_TOK_INEG:
/*
* If we're negating an unsigned integer, zero out any
* extra top bits to truncate the value to the size of
* the effective type determined by dt_node_int().
*/
}
/*FALLTHRU*/
case DT_TOK_IPOS:
return (cp);
case DT_TOK_BNEG:
return (cp);
case DT_TOK_LNEG:
return (cp);
}
}
/*
* If sizeof is applied to a type_name or string constant, we can
* transform 'cp' into an integer constant in the node construction
* pass so that it can then be used for arithmetic in this pass.
*/
if (op == DT_TOK_SIZEOF &&
if (size == 0) {
"operand of unknown size\n");
}
B_FALSE);
return (cp);
}
return (dnp);
}
/*
* If an integer constant is being cast to another integer type, we can
* perform the cast as part of integer constant folding in this pass. We must
* take action when the integer is being cast to a smaller type or if it is
* changing signed-ness. If so, we first shift rp's bits bits high (losing
* excess bits if narrowing) and then shift them down with either a logical
* shift (unsigned) or arithmetic shift (signed).
*/
static void
{
} else {
}
}
}
{
/*
* First we check for operations that are illegal -- namely those that
* might result in integer division by zero, and abort if one is found.
*/
/*
* If both children are immediate values, we can just perform inline
* calculation and return a new immediate node with the result.
*/
switch (op) {
case DT_TOK_LOR:
break;
case DT_TOK_LXOR:
break;
case DT_TOK_LAND:
break;
case DT_TOK_BOR:
break;
case DT_TOK_XOR:
break;
case DT_TOK_BAND:
break;
case DT_TOK_EQU:
break;
case DT_TOK_NEQ:
break;
case DT_TOK_LT:
else
break;
case DT_TOK_LE:
else
break;
case DT_TOK_GT:
else
break;
case DT_TOK_GE:
else
break;
case DT_TOK_LSH:
break;
case DT_TOK_RSH:
break;
case DT_TOK_ADD:
break;
case DT_TOK_SUB:
break;
case DT_TOK_MUL:
break;
case DT_TOK_DIV:
else
break;
case DT_TOK_MOD:
else
break;
default:
}
return (dnp);
}
}
dt_node_is_integer(lp)) {
return (rp);
}
/*
* If no immediate optimizations are available, create an new OP2 node
* and glue the left and right children into place and return.
*/
return (dnp);
}
{
return (dnp);
}
{
return (expr);
else
return (dnp);
}
{
return (dnp);
}
{
}
return (dnp);
}
{
static const char *const names[] = {
"providers", "modules", "functions"
};
}
}
}
return (dnp);
}
{
return (dnp);
}
{
char n[DT_TYPE_NAMELEN];
"appropriate for inline declaration\n");
}
if ((idp = dt_idstack_lookup(
"inline definition\n\tprevious: %s %s\n",
}
/*
* If we are declaring an inlined array, verify that we have a tuple
* signature, and then recompute 'dtt' as the array's value type.
*/
}
}
}
/*
* If the inline identifier is not defined, then create it with the
* orphan flag set. We do not insert the identifier into dt_globals
* until we have successfully cooked the right-hand expression, below.
*/
if (dt_node_is_void(dnp)) {
}
}
}
/*
* If we're inlining an associative array, create a private identifier
* hash containing the named parameters and store it in inp->din_hash.
* We then push this hash on to the top of the pcb_globals stack.
*/
uint_t i = 0;
i++; /* count up parameters for din_argv[] */
/*
* Create an identifier for each parameter as a scalar inline,
* and store it in din_hash and in position in din_argv[]. The
* parameter identifiers also use dt_idops_inline, but we leave
* the dt_idnode_t argument 'pinp' zeroed. This will be filled
* in by the code generation pass with references to the args.
*/
continue; /* ignore anonymous parameters */
}
}
}
/*
* Unlike most constructors, we need to explicitly cook the right-hand
* side of the inline definition immediately to prevent recursion. If
* the right-hand side uses the inline itself, the cook will fail.
*/
/*
* Set the type, attributes, and flags for the inline. If the right-
* hand expression has an identifier, propagate its flags. Then cook
* the identifier to fully initialize it: if we're declaring an inline
* associative array this will construct a type signature from 'ddp'.
*/
if (dt_node_is_dynamic(expr))
else
}
/*
* Store the parse tree nodes for 'expr' inside of idp->di_data ('inp')
* so that they will be preserved with this identifier. Then pop the
* inline declaration from the declaration stack and restore the lexer.
*/
/*
* Finally, insert the inline identifier into dt_globals to make it
* visible, and then cook 'dnp' to check its type against 'expr'.
*/
}
{
int err;
if (err != 0)
}
return (dnp);
}
{
}
"translator from %s to %s has already been declared\n",
}
if (kind == CTF_K_FORWARD) {
}
"translator output type must be a struct or union\n");
}
}
{
(void) strhyphenate(name);
free(s);
"contain scoping operator: %s\n", name);
}
}
"probe output", DT_DP_VOID);
}
}
return (dnp);
}
{
"contain scoping operator: %s\n", name);
}
}
"end with a digit: %s\n", name);
}
/*
* Check to see if the provider is already defined or visible through
* dtrace(7D). If so, set dn_provred to treat it as a re-declaration.
* If not, create a new provider and set its interface-only flag. This
* flag may be cleared later by calls made to dt_probe_declare().
*/
else
/*
* Store all parse nodes created since we consumed the DT_KEY_PROVIDER
* token with the provider and then restore our lexing state to CLAUSE.
* Note that if dnp->dn_provred is true, we may end up storing dups of
* a provider's interface and implementation: we eat this space because
* the implementation will likely need to redeclare probe members, and
* therefore may result in those member nodes becoming persistent.
*/
continue; /* skip to end of allocation list */
return (dnp);
}
{
return (dnp);
}
/*
* This function provides the underlying implementation of cooking an
* identifier given its node, a hash of dynamic identifiers, an identifier
* kind, and a boolean flag indicating whether we are allowed to instantiate
* a new identifier if the string is not found. This function is either
* called from dt_cook_ident(), below, or directly by the various cooking
* routines that are allowed to instantiate identifiers (e.g. op2 TOK_ASGN).
*/
static void
{
int uref = 0;
char *name;
/*
* Look for scoping marks in the identifier. If one is found, set our
* scope to either DTRACE_OBJ_KMODS or UMODS or to the first part of
* the string that specifies the scope using an explicit module name.
* If two marks in a row are found, set 'uref' (user symbol reference).
* Otherwise we set scope to DTRACE_OBJ_EXEC, indicating that normal
* scope is desired and we should search the specified idhash.
*/
uref++;
}
else
} else if (idkind == DT_IDENT_AGG) {
} else {
}
/*
* If create is set to false, and we fail our idhash lookup, preset
* the errno code to EDT_NOVAR for our final error message below.
* If we end up calling dtrace_lookup_by_name(), it will reset the
* errno appropriately and that error will be reported instead.
*/
if (scope == DTRACE_OBJ_EXEC && (
/*
* Check that we are referencing the ident in the manner that
* matches its type if this is a global lookup. In the TLS or
* local case, we don't know how the ident will be used until
* the time operator -> is seen; more parsing is needed.
*/
}
/*
* Arrays and aggregations are not cooked individually. They
* have dynamic types and must be referenced using operator [].
* This is handled explicitly by the code for DT_TOK_LBRAC.
*/
else {
}
}
/*
* For now, we special-case EDT_DATAMODEL to clarify
* that mixed data models are not currently supported.
*/
"%s%s%s in a %s D program\n",
}
"no symbolic type information is available for "
}
if (uref) {
}
}
flags |= DT_IDFLG_LOCAL;
flags |= DT_IDFLG_TLS;
dt_dprintf("create %s %s variable %s, id=%u\n",
} else {
}
/*
* Arrays and aggregations are not cooked individually. They
* have dynamic types and must be referenced using operator [].
* This is handled explicitly by the code for DT_TOK_LBRAC.
*/
else {
}
} else if (scope != DTRACE_OBJ_EXEC) {
} else {
}
}
static dt_node_t *
{
else
}
/*
* Since operators [ and -> can instantiate new variables before we know
* whether the reference is for a read or a write, we need to check read
* references to determine if the identifier is currently dt_ident_unref().
* If so, we report that this first access was to an undefined variable.
*/
static dt_node_t *
{
"%s%s has not yet been declared or assigned\n",
}
return (dnp);
}
/*ARGSUSED*/
static dt_node_t *
{
return (dnp);
}
static dt_node_t *
{
char n[DT_TYPE_NAMELEN];
ctf_arinfo_t r;
else
/*
* We allow the unary ++ and -- operators to instantiate new scalar
* variables if applied to an identifier; otherwise just cook as usual.
*/
}
case DT_TOK_DEREF:
/*
* If the deref operator is applied to a translated pointer,
* we set our output type to the output of the translation.
*/
break;
}
if (kind == CTF_K_ARRAY) {
} else
type = r.ctr_contents;
} else if (kind == CTF_K_POINTER) {
} else {
"cannot dereference non-pointer type\n");
}
"cannot dereference pointer to void\n");
}
if (kind == CTF_K_FUNCTION) {
"cannot dereference pointer to function\n");
}
/*
* If we propagated the l-value bit and the child operand was
* a writable D variable or a binary operation of the form
* a + b where a is writable, then propagate the writable bit.
* This is necessary to permit assignments to scalar arrays,
* which are converted to expressions of the form *(a + i).
*/
break;
case DT_TOK_IPOS:
case DT_TOK_INEG:
if (!dt_node_is_arith(cp)) {
}
break;
case DT_TOK_BNEG:
if (!dt_node_is_integer(cp)) {
}
break;
case DT_TOK_LNEG:
if (!dt_node_is_scalar(cp)) {
}
B_FALSE);
break;
case DT_TOK_ADDROF:
"cannot take address of dynamic variable\n");
}
if (dt_node_is_dynamic(cp)) {
"cannot take address of dynamic object\n");
}
"unacceptable operand for unary & operator\n");
}
"cannot take address of bit-field\n");
}
dt_node_type_name(cp, n, sizeof (n)));
}
break;
case DT_TOK_SIZEOF:
"cannot apply sizeof to a bit-field\n");
}
if (dt_node_sizeof(cp) == 0) {
"operand of unknown size\n");
}
B_FALSE);
break;
case DT_TOK_STRINGOF:
!dt_node_is_strcompat(cp)) {
"cannot apply stringof to a value of type %s\n",
dt_node_type_name(cp, n, sizeof (n)));
}
break;
case DT_TOK_PREINC:
case DT_TOK_POSTINC:
case DT_TOK_PREDEC:
case DT_TOK_POSTDEC:
if (dt_node_is_scalar(cp) == 0) {
}
if (dt_node_is_vfptr(cp)) {
}
}
}
break;
default:
}
return (dnp);
}
static void
{
/* see K&R[A7.17] */
}
}
}
static dt_node_t *
{
/*
* The expression E1[E2] is identical by definition to *((E1)+(E2)) so
* we convert "[" to "+" and glue on "*" at the end (see K&R[A7.3.1])
* unless the left-hand side is an untyped D scalar, associative array,
* or aggregation. In these cases, we proceed to case DT_TOK_LBRAC and
* handle associative array and aggregation references there.
*/
if (op == DT_TOK_LBRAC) {
} else {
}
else
} else {
}
/*
* Switch op to '+' for *(E1 + E2) array mode in these cases:
* (a) lp is a DT_IDENT_ARRAY variable that has already been
* referenced using [] notation (dn_args != NULL).
* (b) lp is a non-ARRAY variable that has already been given
* a type by assignment or declaration (!dt_ident_unref())
* (c) lp is neither a variable nor an aggregation
*/
op = DT_TOK_ADD;
op = DT_TOK_ADD;
}
op = DT_TOK_ADD;
}
}
switch (op) {
case DT_TOK_BAND:
case DT_TOK_XOR:
case DT_TOK_BOR:
}
break;
case DT_TOK_LSH:
case DT_TOK_RSH:
}
break;
case DT_TOK_MOD:
}
break;
case DT_TOK_MUL:
case DT_TOK_DIV:
}
break;
case DT_TOK_LAND:
case DT_TOK_LXOR:
case DT_TOK_LOR:
}
B_FALSE);
break;
case DT_TOK_LT:
case DT_TOK_LE:
case DT_TOK_GT:
case DT_TOK_GE:
case DT_TOK_EQU:
case DT_TOK_NEQ:
/*
* The D comparison operators provide the ability to transform
* a right-hand identifier into a corresponding enum tag value
* if the left-hand side is an enum type. To do this, we cook
* the left-hand side, and then see if the right-hand side is
* an unscoped identifier defined in the enum. If so, we
* convert into an integer constant node with the tag's value.
*/
"ambiguous use of operator %s: %s is "
"both a %s enum tag and a global %s\n",
}
B_FALSE);
}
/*
* The rules for type checking for the relational operators are
* described in the ANSI-C spec (see K&R[A7.9-10]). We perform
* the various tests in order from least to most expensive. We
* also allow derived strings to be compared as a first-class
* type (resulting in a strcmp(3C)-style comparison), and we
* slightly relax the A7.9 rules to permit void pointer
* comparisons as in A7.10. Our users won't be confused by
* this since they understand pointers are just numbers, and
* relaxing this constraint simplifies the implementation.
*/
/*EMPTY*/;
/*EMPTY*/;
/*EMPTY*/;
"incompatible types: \"%s\" %s \"%s\"\n",
}
B_FALSE);
break;
case DT_TOK_ADD:
case DT_TOK_SUB: {
/*
* The rules for type checking for the additive operators are
* described in the ANSI-C spec (see K&R[A7.7]). Pointers and
* integers may be manipulated according to specific rules. In
* these cases D permits strings to be treated as pointers.
*/
uref = 0;
uref = 0;
} else {
"types: \"%s\" %s \"%s\"\n",
}
if (uref)
break;
}
case DT_TOK_OR_EQ:
case DT_TOK_XOR_EQ:
case DT_TOK_AND_EQ:
case DT_TOK_LSH_EQ:
case DT_TOK_RSH_EQ:
case DT_TOK_MOD_EQ:
}
}
goto asgn_common;
case DT_TOK_MUL_EQ:
case DT_TOK_DIV_EQ:
}
}
goto asgn_common;
case DT_TOK_ASGN:
/*
* If the left-hand side is an identifier, attempt to resolve
* it as either an aggregation or scalar variable. We pass
* B_TRUE to dt_xcook_ident to indicate that a new variable can
* be created if no matching variable exists in the namespace.
*/
} else {
}
}
/*
* If the left-hand side is an aggregation, verify that we are
* assigning it the result of an aggregating function. Once
* we've done so, hide the func node in the aggregation and
* return the aggregation itself up to the parse tree parent.
* This transformation is legal since the assigned function
* cannot change identity across disjoint cooking passes and
* the argument list subtree is retained for later cooking.
*/
"@%s must be assigned the result of "
"an aggregating function\n", aname);
}
"aggregation redefined: @%s\n\t "
"current: @%s = %s( )\n\tprevious: @%s = "
/*
* Do not allow multiple aggregation assignments in a
* single statement, e.g. (@a = count()) = count();
* We produce a message as if the result of aggregating
* function does not propagate DT_NF_LVALUE.
*/
"modifiable lvalue as an operand\n");
}
return (lp);
}
/*
* If the right-hand side is a dynamic variable that is the
* output of a translator, our result is the translated type.
*/
} else {
}
/*
* If the left-hand side of an assignment statement is a virgin
* variable created by this compilation pass, reset the type of
* this variable to the type of the right-hand side.
*/
if (uref) {
}
}
/*
* The rules for type checking for the assignment operators are
* described in the ANSI-C spec (see K&R[A7.17]). We share
* most of this code with the argument list checking code.
*/
if (!dt_node_is_string(lp)) {
"applied to operand of type \"%s\"\n",
}
}
goto asgn_common;
goto asgn_common;
"operands have incompatible types: \"%s\" %s \"%s\"\n",
/*NOTREACHED*/
case DT_TOK_ADD_EQ:
case DT_TOK_SUB_EQ:
}
"incompatible types: \"%s\" %s \"%s\"\n",
}
/*
* The rules for type checking for the assignment operators are
* described in the ANSI-C spec (see K&R[A7.17]). To these
* rules we add that only writable D nodes can be modified.
*/
if (dt_node_is_integer(lp) == 0 ||
dt_node_is_integer(rp) == 0) {
"operator %s requires left-hand scalar "
} else if (dt_node_is_integer(rp) == 0 &&
"incompatible types: \"%s\" %s \"%s\"\n",
}
}
break;
case DT_TOK_PTR:
/*
* If the left-hand side of operator -> is one of the scoping
* keywords, permit a local or thread variable to be created or
* referenced.
*/
}
}
if (idflags != 0)
/* avoid freeing rp */
return (rp);
}
}
/*FALLTHRU*/
case DT_TOK_DOT:
}
/*
* If the left-hand side is a translated struct or ptr,
* the type of the left is the translation output type.
*/
"translator does not define conversion "
}
} else {
}
if (op == DT_TOK_PTR) {
if (kind != CTF_K_POINTER) {
}
}
/*
* If we follow a reference to a forward declaration tag,
* search the entire type space for the actual definition.
*/
while (kind == CTF_K_FORWARD) {
} else {
"operator %s cannot be applied to a "
"forward declaration: no %s definition "
}
}
if (op == DT_TOK_PTR) {
"applied to pointer to type \"%s\"; must "
"be applied to a struct or union pointer\n",
} else {
"applied to type \"%s\"; must be applied "
}
}
}
break;
case DT_TOK_LBRAC: {
/*
* If op is DT_TOK_LBRAC, we know from the special-case code at
* the top that lp is either a D variable or an aggregation.
*/
/*
* If the left-hand side is an aggregation, just set dn_aggtup
* to the right-hand side and return the cooked aggregation.
* This transformation is legal since we are just collapsing
* nodes to simplify later processing, and the entire aggtup
* parse subtree is retained for subsequent cooking passes.
*/
"reference @%s as a multi-dimensional "
}
return (lp);
}
/*
* If the left-hand side is a non-global scalar that hasn't yet
* been referenced or modified, it was just created by self->
* or this-> and we can convert it from scalar to assoc array.
*/
"local variables may not be used as "
}
dt_dprintf("morph variable %s (id %u) from scalar to "
&dt_idops_assc, NULL);
}
}
/*
* Now that we've confirmed our left-hand side is a DT_NODE_VAR
* of idkind DT_IDENT_ARRAY, we need to splice the [ node from
* the parse tree and leave a cooked DT_NODE_VAR in its place
* where dn_args for the VAR node is the right-hand 'rp' tree,
* as shown in the parse tree diagram below:
*
* / /
* [ OP2 "[" ]=dnp [ VAR ]=dnp
* / \ => |
* / \ +- dn_args -> [ ??? ]=rp
* [ VAR ]=lp [ ??? ]=rp
*
* Since the final dt_node_cook(dnp) can fail using longjmp we
* must perform the transformations as a group first by over-
* writing 'dnp' to become the VAR node, so that the parse tree
* is guaranteed to be in a consistent state if the cook fails.
*/
}
case DT_TOK_XLATE: {
"cannot translate from \"%s\" to \"%s\"\n",
}
B_FALSE);
break;
}
case DT_TOK_LPAR: {
/*
* The rules for casting are loosely explained in K&R[A7.5]
* and K&R[A6]. Basically, we can cast to the same type or
* same base type, between any kind of scalar values, from
* arrays to pointers, and we can cast anything to void.
* To these rules D adds casts from scalars to strings.
*/
/*EMPTY*/;
else if (dt_node_is_scalar(lp) &&
/*EMPTY*/;
else if (dt_node_is_void(lp))
/*EMPTY*/;
/*EMPTY*/;
/*EMPTY*/;
else {
"invalid cast expression: \"%s\" to \"%s\"\n",
}
/*
* If it's a pointer then should be able to (attempt to)
* assign to it.
*/
if (lkind == CTF_K_POINTER)
break;
}
case DT_TOK_COMMA:
}
}
break;
default:
}
/*
* Complete the conversion of E1[E2] to *((E1)+(E2)) that we started
* at the top of our switch() above (see K&R[A7.3.1]). Since E2 is
* parsed as an argument_expression_list by dt_grammar.y, we can
* end up with a comma-separated list inside of a non-associative
* array reference. We check for this and report an appropriate error.
*/
"cannot access %s as an associative array\n",
}
/*
* Cook callbacks are not typically permitted to allocate nodes.
* When we do, we must insert them in the middle of an existing
* allocation list rather than having them appended to the pcb
* list because the sub-expression may be part of a definition.
*/
}
return (dnp);
}
/*ARGSUSED*/
static dt_node_t *
{
"operator ?: expression must be of scalar type\n");
}
"operator ?: operands cannot be of dynamic type\n");
}
/*
* The rules for type checking for the ternary operator are complex and
* are described in the ANSI-C spec (see K&R[A7.16]). We implement
* the various tests in order from least to most expensive.
*/
"operator ?: operands must have compatible types\n");
}
"used in a conditional context\n");
}
return (dnp);
}
static dt_node_t *
{
return (dnp);
}
/*
* If dn_aggfun is set, this node is a collapsed aggregation assignment (see
* the special case code for DT_TOK_ASGN in dt_cook_op2() above), in which
* case we cook both the tuple and the function call. If dn_aggfun is NULL,
* this node is just a reference to the aggregation's type and attributes.
*/
/*ARGSUSED*/
static dt_node_t *
{
} else {
B_FALSE);
}
return (dnp);
}
/*
* Since D permits new variable identifiers to be instantiated in any program
* expression, we may need to cook a clause's predicate either before or after
* the action list depending on the program code in question. Consider:
*
* probe-description-list probe-description-list
* /x++/ /x == 0/
* { {
* trace(x); trace(x++);
* } }
*
* In the left-hand example, the predicate uses operator ++ to instantiate 'x'
* as a variable of type int64_t. The predicate must be cooked first because
* otherwise the statement trace(x) refers to an unknown identifier. In the
* right-hand example, the action list uses ++ to instantiate 'x'; the action
* list must be cooked first because otherwise the predicate x == 0 refers to
* an unknown identifier. In order to simplify programming, we support both.
*
* When cooking a clause, we cook the action statements before the predicate by
* default, since it seems more common to create or modify identifiers in the
* action list. If cooking fails due to an unknown identifier, we attempt to
* cook the predicate (i.e. do it first) and then go back and cook the actions.
* If this, too, fails (or if we get an error other than D_IDENT_UNDEF) we give
* up and report failure back to the user. There are five possible paths:
*
* cook actions = OK, cook predicate = OK -> OK
* cook actions = OK, cook predicate = ERR -> ERR
* cook actions = ERR, cook predicate = ERR -> ERR
* cook actions = ERR, cook predicate = OK, cook actions = OK -> OK
* cook actions = ERR, cook predicate = OK, cook actions = ERR -> ERR
*
* The programmer can still defeat our scheme by creating circular definition
* dependencies between predicates and actions, as in this example clause:
*
* probe-description-list
* /x++ && y == 0/
* {
* trace(x + y++);
* }
*
* but it doesn't seem worth the complexity to handle such rare cases. The
* user can simply use the D variable declaration syntax to work around them.
*/
static dt_node_t *
{
/*
* Before assigning dn_ctxattr, temporarily assign the probe attribute
* to 'dnp' itself to force an attribute check and minimum violation.
*/
tries = 0;
}
if (tries == 0) {
yylabel("action list");
}
yylabel("predicate");
"predicate result must be of scalar type\n");
}
}
if (tries != 0) {
yylabel("action list");
}
return (dnp);
}
/*ARGSUSED*/
static dt_node_t *
{
/*
* If we are inlining a translation, verify that the inline declaration
* type exactly matches the type that is returned by the translation.
* Otherwise just use dt_node_is_argcompat() to check the types.
*/
}
"inline %s definition uses incompatible types: "
}
"inline %s definition uses incompatible types: "
}
return (dnp);
}
static dt_node_t *
{
return (dnp);
}
/*ARGSUSED*/
static dt_node_t *
{
/*
* Before cooking each translator member, we push a reference to the
* hash containing translator-local identifiers on to pcb_globals to
* temporarily interpose these identifiers in front of other globals.
*/
"translator member %s is not a member of %s\n",
}
B_FALSE);
"translator member %s definition uses "
"incompatible types: \"%s\" = \"%s\"\n",
}
}
return (dnp);
}
static void
{
uint_t i;
"probe %s:%s %s prototype mismatch:\n"
"\t current: %u arg%s\n\tprevious: %u arg%s\n",
}
for (i = 0; i < old_argc; i++,
continue;
"probe %s:%s %s prototype argument #%u mismatch:\n"
"\t current: %s\n\tprevious: %s\n",
}
}
/*
* Compare a new probe declaration with an existing probe definition (either
* from a previous declaration or cached from the kernel). If the existing
* definition and declaration both have an input and output parameter list,
* compare both lists. Otherwise compare only the output parameter lists.
*/
static void
{
}
"provider interface mismatch: %s\n"
"\t current: probe %s:%s has an output prototype\n"
"\tprevious: probe %s:%s has no output prototype\n",
}
}
}
static void
{
uint_t i;
return;
continue;
}
continue; /* no translator defined and none required */
"argument #%u from %s to %s is not defined\n",
}
}
/*ARGSUSED*/
static dt_node_t *
{
/*
* If we're declaring a provider for the first time and it is unknown
* to dtrace(7D), insert the probe definitions into the provider's hash.
* If we're redeclaring a known provider, verify the interface matches.
*/
"provider interface mismatch: %s\n"
"\t current: probe %s:%s defined\n"
"\tprevious: probe %s:%s not defined\n",
} else
}
return (dnp);
}
/*ARGSUSED*/
static dt_node_t *
{
return (dnp);
}
dt_cook_none, /* DT_NODE_FREE */
dt_cook_none, /* DT_NODE_INT */
dt_cook_none, /* DT_NODE_STRING */
dt_cook_ident, /* DT_NODE_IDENT */
dt_cook_var, /* DT_NODE_VAR */
dt_cook_none, /* DT_NODE_SYM */
dt_cook_none, /* DT_NODE_TYPE */
dt_cook_func, /* DT_NODE_FUNC */
dt_cook_op1, /* DT_NODE_OP1 */
dt_cook_op2, /* DT_NODE_OP2 */
dt_cook_op3, /* DT_NODE_OP3 */
dt_cook_statement, /* DT_NODE_DEXPR */
dt_cook_statement, /* DT_NODE_DFUNC */
dt_cook_aggregation, /* DT_NODE_AGG */
dt_cook_none, /* DT_NODE_PDESC */
dt_cook_clause, /* DT_NODE_CLAUSE */
dt_cook_inline, /* DT_NODE_INLINE */
dt_cook_member, /* DT_NODE_MEMBER */
dt_cook_xlator, /* DT_NODE_XLATOR */
dt_cook_none, /* DT_NODE_PROBE */
dt_cook_provider, /* DT_NODE_PROVIDER */
dt_cook_none, /* DT_NODE_PROG */
dt_cook_none, /* DT_NODE_IF */
};
/*
* Recursively cook the parse tree starting at the specified node. The idflags
* parameter is used to indicate the type of reference (r/w) and is applied to
* the resulting identifier if it is a D variable or D aggregation.
*/
{
sizeof (dt_cook_funcs) / sizeof (dt_cook_funcs[0]));
return (dnp);
}
{
}
return (attr);
}
void
{
}
}
void
{
}
}
}
{
return (rp);
return (lp);
continue;
return (lp);
}
/*
* Compute the DOF dtrace_diftype_t representation of a node's type. This is
* called from a variety of places in the library so it cannot assume yypcb
* is valid: any references to handle-specific data must be made through 'dtp'.
*/
void
{
} else {
}
DIF_TF_BYREF : 0;
}
/*
* Output the parse tree as D. The "-xtree=8" argument will call this
* function to print out the program after any syntactic sugar
* transformations have been applied (e.g. to implement "if"). The
* resulting output can be used to understand the transformations
* applied by these features, or to run such a script on a system that
* does not support these features
*
* Note that the output does not express precisely the same program as
* the input. In particular:
* - Only the clauses are output. #pragma options, variable
* declarations, etc. are excluded.
* - Command argument substitution has already been done, so the output
* will not contain e.g. $$1, but rather the substituted string.
*/
void
{
case DT_NODE_INT:
break;
case DT_NODE_STRING: {
break;
}
case DT_NODE_IDENT:
break;
case DT_NODE_VAR:
}
}
break;
case DT_NODE_SYM: {
break;
}
case DT_NODE_FUNC:
}
break;
case DT_NODE_OP1:
break;
case DT_NODE_OP2:
break;
}
else
}
}
break;
case DT_NODE_OP3:
break;
case DT_NODE_DEXPR:
case DT_NODE_DFUNC:
break;
case DT_NODE_PDESC:
break;
case DT_NODE_CLAUSE:
}
}
break;
case DT_NODE_IF:
} else {
}
break;
default:
}
}
void
{
} else {
}
n[0] = '\0';
(void) strcat(n, ",SIGN");
(void) strcat(n, ",COOK");
(void) strcat(n, ",REF");
(void) strcat(n, ",LVAL");
(void) strcat(n, ",WRITE");
(void) strcat(n, ",BITF");
(void) strcat(n, ",USER");
} else
case DT_NODE_FREE:
break;
case DT_NODE_INT:
break;
case DT_NODE_STRING:
break;
case DT_NODE_IDENT:
break;
case DT_NODE_VAR:
}
break;
case DT_NODE_SYM:
break;
case DT_NODE_TYPE:
} else
break;
case DT_NODE_FUNC:
}
break;
case DT_NODE_OP1:
break;
case DT_NODE_OP2:
}
}
break;
case DT_NODE_OP3:
break;
case DT_NODE_DEXPR:
case DT_NODE_DFUNC:
break;
case DT_NODE_AGG:
}
} else
break;
case DT_NODE_PDESC:
break;
case DT_NODE_CLAUSE:
}
break;
case DT_NODE_INLINE:
break;
case DT_NODE_MEMBER:
if (dnp->dn_membexpr)
break;
case DT_NODE_XLATOR:
break;
case DT_NODE_PROBE:
break;
case DT_NODE_PROVIDER:
break;
case DT_NODE_PROG:
break;
case DT_NODE_IF:
}
break;
default:
}
}
int
{
return (0);
}
/*PRINTFLIKE3*/
void
{
}
/*PRINTFLIKE3*/
void
{
}
/*PRINTFLIKE2*/
void
{
}
/*PRINTFLIKE2*/
void
{
}
void
{
return; /* compiler is not currently active: act as a no-op */
}
/*PRINTFLIKE1*/
void
{
}
/*PRINTFLIKE1*/
void
{
}
void
{
return; /* compiler is not currently active: act as a no-op */
if (yytext[0] == '\0')
(void) snprintf(s, n, " near end of input");
else if (yytext[0] == '\n')
(void) snprintf(s, n, " near end of line");
else {
*p = '\0'; /* crop at newline */
}
}
}
void
{
}
int
yywrap(void)
{
return (1); /* indicate that lex should return a zero token for EOF */
}