/*------------------------------------------------------------------------- * * expr_walk.c * * set of Query/Expr walkers * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "nodes/nodeFuncs.h" #include "catalog/pg_class.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "nodes/nodeFuncs.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #if PG_VERSION_NUM < 90600 #include "optimizer/planmain.h" #endif typedef struct { PLpgSQL_checkstate *cstate; PLpgSQL_expr *expr; char *query_str; } check_funcexpr_walker_params; typedef bool (*check_function_callback) (Oid func_id, void *context); static int check_fmt_string(const char *fmt, List *args, int location, check_funcexpr_walker_params *wp, bool *is_error, int *unsafe_expr_location, bool no_error); /* * Send to ouput all not yet displayed relations and functions. */ static bool detect_dependency_walker(Node *node, void *context) { PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; plpgsql_check_result_info *ri = cstate->result_info; if (node == NULL) return false; if (IsA(node, Query)) { Query *query = (Query *) node; ListCell *lc; foreach(lc, query->rtable) { RangeTblEntry *rt = (RangeTblEntry *) lfirst(lc); if (rt->rtekind == RTE_RELATION) { if (!bms_is_member(rt->relid, cstate->rel_oids)) { plpgsql_check_put_dependency(ri, "RELATION", rt->relid, get_namespace_name(get_rel_namespace(rt->relid)), get_rel_name(rt->relid), NULL); cstate->rel_oids = bms_add_member(cstate->rel_oids, rt->relid); } } } return query_tree_walker((Query *) node, detect_dependency_walker, context, 0); } if (IsA(node, FuncExpr) ) { FuncExpr *fexpr = (FuncExpr *) node; if (get_func_namespace(fexpr->funcid) != PG_CATALOG_NAMESPACE) { if (!bms_is_member(fexpr->funcid, cstate->func_oids)) { StringInfoData str; ListCell *lc; int i = 0; initStringInfo(&str); appendStringInfoChar(&str, '('); foreach(lc, fexpr->args) { Node *expr = (Node *) lfirst(lc); if (i++ > 0) appendStringInfoChar(&str, ','); appendStringInfoString(&str, format_type_be(exprType(expr))); } appendStringInfoChar(&str, ')'); plpgsql_check_put_dependency(ri, "FUNCTION", fexpr->funcid, get_namespace_name(get_func_namespace(fexpr->funcid)), get_func_name(fexpr->funcid), str.data); pfree(str.data); cstate->func_oids = bms_add_member(cstate->func_oids, fexpr->funcid); } } } return expression_tree_walker(node, detect_dependency_walker, context); } void plpgsql_check_detect_dependency(PLpgSQL_checkstate *cstate, Query *query) { if (cstate->result_info->format != PLPGSQL_SHOW_DEPENDENCY_FORMAT_TABULAR) return; detect_dependency_walker((Node *) query, cstate); } /* * Expecting persistent oid of nextval, currval and setval functions. * Ensured by regress tests. */ #define NEXTVAL_OID 1574 #define CURRVAL_OID 1575 #define SETVAL_OID 1576 #define SETVAL2_OID 1765 #define FORMAT_0PARAM_OID 3540 #define FORMAT_NPARAM_OID 3539 /* * When sequence related functions has constant oid parameter, then ensure, so * this oid is related to some sequnce object. * */ static bool check_funcexpr_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Query)) { return query_tree_walker((Query *) node, check_funcexpr_walker, context, 0); } if (IsA(node, FuncExpr)) { FuncExpr *fexpr = (FuncExpr *) node; int location = -1; switch (fexpr->funcid) { case NEXTVAL_OID: case CURRVAL_OID: case SETVAL_OID: case SETVAL2_OID: { Node *first_arg = linitial(fexpr->args); location = fexpr->location; if (first_arg && IsA(first_arg, Const)) { Const *c = (Const *) first_arg; if (c->consttype == REGCLASSOID && !c->constisnull) { Oid classid; if (c->location != -1) location = c->location; classid = DatumGetObjectId(c->constvalue); if (get_rel_relkind(classid) != RELKIND_SEQUENCE) { char message[1024]; check_funcexpr_walker_params *wp; wp = (check_funcexpr_walker_params *) context; snprintf(message, sizeof(message), "\"%s\" is not a sequence", get_rel_name(classid)); plpgsql_check_put_error(wp->cstate, ERRCODE_WRONG_OBJECT_TYPE, 0, message, NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); } } } } break; case FORMAT_0PARAM_OID: case FORMAT_NPARAM_OID: { /* We can do check only when first argument is constant */ Node *first_arg = linitial(fexpr->args); if (first_arg && IsA(first_arg, Const)) { Const *c = (Const *) first_arg; if (c->consttype == TEXTOID && !c->constisnull) { char *fmt = TextDatumGetCString(c->constvalue); check_funcexpr_walker_params *wp; int required_nargs; bool is_error; wp = (check_funcexpr_walker_params *) context; required_nargs = check_fmt_string(fmt, fexpr->args, c->location, wp, &is_error, NULL, false); if (!is_error && required_nargs != -1) { if (required_nargs + 1 != list_length(fexpr->args)) plpgsql_check_put_error(wp->cstate, 0, 0, "unused parameters of function \"format\"", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, c->location, wp->query_str, NULL); } } } } } } return expression_tree_walker(node, check_funcexpr_walker, context); } void plpgsql_check_funcexpr(PLpgSQL_checkstate *cstate, Query *query, char *query_str) { check_funcexpr_walker_params wp; wp.cstate = cstate; wp.query_str = query_str; wp.expr = NULL; check_funcexpr_walker((Node *) query, &wp); } /* * Aux functions for checking format string of function format */ /* * Support macros for text_format() */ #define ADVANCE_PARSE_POINTER(ptr,end_ptr) \ do { \ if (++(ptr) >= (end_ptr)) \ { \ if (wp) \ plpgsql_check_put_error(wp->cstate, \ ERRCODE_INVALID_PARAMETER_VALUE, 0, \ "unterminated format() type specifier", \ NULL, \ "For a single \"%%\" use \"%%%%\".", \ PLPGSQL_CHECK_ERROR, \ location, \ wp->query_str, NULL); \ *is_error = true; \ } \ } while (0) /* * Parse contiguous digits as a decimal number. * * Returns true if some digits could be parsed. * The value is returned into *value, and *ptr is advanced to the next * character to be parsed. */ static bool text_format_parse_digits(const char **ptr, const char *end_ptr, int *value, int location, check_funcexpr_walker_params *wp, bool *is_error) { bool found = false; const char *cp = *ptr; int val = 0; *is_error = false; while (*cp >= '0' && *cp <= '9') { int newval = val * 10 + (*cp - '0'); if (newval / 10 != val) /* overflow? */ { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, 0, "number is out of range", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return false; } val = newval; ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return false; found = true; } *ptr = cp; *value = val; return found; } /* * reduced code from varlena.c */ static const char * text_format_parse_format(const char *start_ptr, const char *end_ptr, int *argpos, int *widthpos, int location, check_funcexpr_walker_params *wp, bool *is_error) { const char *cp = start_ptr; int n; bool found; /* set defaults for output parameters */ *argpos = -1; *widthpos = -1; *is_error = false; /* try to identify first number */ found = text_format_parse_digits(&cp, end_ptr, &n, location, wp, is_error); if (*is_error) return NULL; if (found) { if (*cp != '$') { /* Must be just a width and a type, so we're done */ return cp; } /* The number was argument position */ *argpos = n; /* Explicit 0 for argument index is immediately refused */ if (n == 0) { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, "format specifies argument 0, but arguments are numbered from 1", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return NULL; } ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; } /* Handle flags (only minus is supported now) */ while (*cp == '-') { ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; } if (*cp == '*') { /* Handle indirect width */ ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; found = text_format_parse_digits(&cp, end_ptr, &n, location, wp, is_error); if (*is_error) return NULL; if (found) { /* number in this position must be closed by $ */ if (*cp != '$') { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, "width argument position must be ended by \"$\"", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return NULL; } /* The number was width argument position */ *widthpos = n; /* Explicit 0 for argument index is immediately refused */ if (n == 0) { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, "format specifies argument 0, but arguments are numbered from 1", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return NULL; } ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; } else *widthpos = 0; /* width's argument position is unspecified */ } else { /* Check for direct width specification */ found = text_format_parse_digits(&cp, end_ptr, &n, location, wp, is_error); if (*is_error) return NULL; } /* cp should now be pointing at type character */ return cp; } #define TOO_FEW_ARGUMENTS_CHECK(arg, nargs) \ if (arg > nargs) \ { \ if (wp) \ plpgsql_check_put_error(wp->cstate, \ ERRCODE_INVALID_PARAMETER_VALUE, 0, \ "too few arguments for format()", \ NULL, \ NULL, \ PLPGSQL_CHECK_ERROR, \ location, \ wp->query_str, NULL); \ *is_error = true; \ return -1; \ } /* * Returns number of rquired arguments or -1 when we cannot detect this number. * When no_error is true, then this function doesn't raise a error or warning. */ static int check_fmt_string(const char *fmt, List *args, int location, check_funcexpr_walker_params *wp, bool *is_error, int *unsafe_expr_location, bool no_error) { const char *cp; const char *end_ptr = fmt + strlen(fmt); int nargs = list_length(args); int required_nargs = 0; int arg = 1; /* Scan format string, looking for conversion specifiers. */ for (cp = fmt; cp < end_ptr; cp++) { int argpos; int widthpos; if (*cp != '%') continue; ADVANCE_PARSE_POINTER(cp, end_ptr); if (*cp == '%') continue; /* Parse the optional portions of the format specifier */ cp = text_format_parse_format(cp, end_ptr, &argpos, &widthpos, location, wp, is_error); if (*is_error) return -1; /* * Next we should see the main conversion specifier. Whether or not * an argument position was present, it's known that at least one * character remains in the string at this point. Experience suggests * that it's worth checking that that character is one of the expected * ones before we try to fetch arguments, so as to produce the least * confusing response to a mis-formatted specifier. */ if (strchr("sIL", *cp) == NULL) { StringInfoData sinfo; initStringInfo(&sinfo); appendStringInfo(&sinfo, "unrecognized format() type specifier \"%c\"", *cp); if (!no_error) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, sinfo.data, NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); pfree(sinfo.data); *is_error = true; return -1; } if (widthpos >= 0) { if (widthpos > 0) { TOO_FEW_ARGUMENTS_CHECK(widthpos, nargs); required_nargs = -1; } else { TOO_FEW_ARGUMENTS_CHECK(++arg, nargs); if (required_nargs != -1) required_nargs += 1; } } /* Check safety of argument againt SQL injection when it is required */ if (unsafe_expr_location) { if (*cp == 's') { int argn = argpos >= 1 ? argpos : arg + 1; /* this is usually called after format check, but better be safe*/ if (argn <= nargs) { Node *arg = list_nth(args, argn - 1); if (plpgsql_check_is_sql_injection_vulnerable(wp->cstate, wp->expr, arg, unsafe_expr_location)) { /* found vulnerability, stop */ *is_error = false; return -1; } } } } if (argpos >= 1) { TOO_FEW_ARGUMENTS_CHECK(argpos, nargs); required_nargs = -1; } else { TOO_FEW_ARGUMENTS_CHECK(++arg, nargs); if (required_nargs != -1) required_nargs += 1; } } return required_nargs; } /* * Try to detect relations in query */ static bool has_rtable_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Query)) { Query *query = (Query *) node; bool has_relation = false; ListCell *lc; foreach (lc, query->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); if (rte->rtekind == RTE_RELATION) { has_relation = true; break; } } if (has_relation) { return true; } else return query_tree_walker(query, has_rtable_walker, context, 0); } return expression_tree_walker(node, has_rtable_walker, context); } /* * Returns true, if query use any relation */ bool plpgsql_check_has_rtable(Query *query) { return has_rtable_walker((Node *) query, NULL); } /* * Try to identify constraint where variable from one side is implicitly casted to * parameter type of second side. It can symptom of parameter wrong type. * */ static bool contain_fishy_cast_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, OpExpr)) { OpExpr *opexpr = (OpExpr *) node; if (!opexpr->opretset && opexpr->opresulttype == BOOLOID && list_length(opexpr->args) == 2) { Node *l1 = linitial(opexpr->args); Node *l2 = lsecond(opexpr->args); Param *param = NULL; FuncExpr *fexpr = NULL; if (IsA(l1, Param)) param = (Param *) l1; else if (IsA(l1, FuncExpr)) fexpr = (FuncExpr *) l1; if (IsA(l2, Param)) param = (Param *) l2; else if (IsA(l2, FuncExpr)) fexpr = (FuncExpr *) l2; if (param != NULL && fexpr != NULL) { if (param->paramkind != PARAM_EXTERN) return false; if (fexpr->funcformat != COERCE_IMPLICIT_CAST || fexpr->funcretset || list_length(fexpr->args) != 1 || param->paramtype != fexpr->funcresulttype) return false; if (!IsA(linitial(fexpr->args), Var)) return false; *((Param **) context) = param; return true; } } } return expression_tree_walker(node, contain_fishy_cast_walker, context); } bool plpgsql_check_qual_has_fishy_cast(PlannedStmt *plannedstmt, Plan *plan, Param **param) { ListCell *lc; if (plan == NULL) return false; if (contain_fishy_cast_walker((Node *) plan->qual, param)) return true; if (plpgsql_check_qual_has_fishy_cast(plannedstmt, innerPlan(plan), param)) return true; if (plpgsql_check_qual_has_fishy_cast(plannedstmt, outerPlan(plan), param)) return true; foreach(lc, plan->initPlan) { SubPlan *subplan = (SubPlan *) lfirst(lc); Plan *s_plan = exec_subplan_get_plan(plannedstmt, subplan); if (plpgsql_check_qual_has_fishy_cast(plannedstmt, s_plan, param)) return true; } return false; } #define QUOTE_IDENT_OID 1282 #define QUOTE_LITERAL_OID 1283 #define QUOTE_NULLABLE_OID 1289 /* * Recursive iterate to deep and search extern params with typcategory "S", and check * if this value is sanitized. Flag is_safe is true, when result is safe. */ bool plpgsql_check_is_sql_injection_vulnerable(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, Node *node, int *location) { if (IsA(node, FuncExpr)) { FuncExpr *fexpr = (FuncExpr *) node; bool is_vulnerable = false; ListCell *lc; foreach(lc, fexpr->args) { Node *arg = lfirst(lc); if (plpgsql_check_is_sql_injection_vulnerable(cstate, expr, arg, location)) { is_vulnerable = true; break; } } if (is_vulnerable) { bool typispreferred; char typcategory; get_type_category_preferred(fexpr->funcresulttype, &typcategory, &typispreferred); if (typcategory == 'S') { switch (fexpr->funcid) { case QUOTE_IDENT_OID: case QUOTE_LITERAL_OID: case QUOTE_NULLABLE_OID: return false; case FORMAT_0PARAM_OID: case FORMAT_NPARAM_OID: { /* We can do check only when first argument is constant */ Node *first_arg = linitial(fexpr->args); if (first_arg && IsA(first_arg, Const)) { Const *c = (Const *) first_arg; if (c->consttype == TEXTOID && !c->constisnull) { char *fmt = TextDatumGetCString(c->constvalue); check_funcexpr_walker_params wp; bool is_error; wp.cstate = cstate; wp.expr = expr; wp.query_str = expr->query; *location = -1; check_fmt_string(fmt, fexpr->args, c->location, &wp, &is_error, location, true); /* only in this case, "format" function obviously sanitize parameters */ if (!is_error) return *location != -1; } } } break; default: /* do nothing */ ; } return true; } } return false; } else if (IsA(node, OpExpr)) { OpExpr *op = (OpExpr *) node; bool is_vulnerable = false; ListCell *lc; foreach(lc, op->args) { Node *arg = lfirst(lc); if (plpgsql_check_is_sql_injection_vulnerable(cstate, expr, arg, location)) { is_vulnerable = true; break; } } if (is_vulnerable) { bool typispreferred; char typcategory; get_type_category_preferred(op->opresulttype, &typcategory, &typispreferred); if (typcategory == 'S') { char *opname = get_opname(op->opno); bool result = false; if (opname) { result = strcmp(opname, "||") == 0; pfree(opname); } return result; } } return false; } else if (IsA(node, NamedArgExpr)) { return plpgsql_check_is_sql_injection_vulnerable(cstate, expr, (Node *) ((NamedArgExpr *) node)->arg, location); } else if (IsA(node, RelabelType)) { return plpgsql_check_is_sql_injection_vulnerable(cstate, expr, (Node *) ((RelabelType *) node)->arg, location); } else if (IsA(node, Param)) { Param *p = (Param *) node; if (p->paramkind == PARAM_EXTERN || p->paramkind == PARAM_EXEC) { bool typispreferred; char typcategory; get_type_category_preferred(p->paramtype, &typcategory, &typispreferred); if (typcategory == 'S') { if (p->paramkind == PARAM_EXTERN && p->paramid > 0 && p->location != -1) { int dno = p->paramid - 1; /* * When paramid looks well and related datum is variable with same * type, then we can check, if this variable has sanitized content * already. */ if (expr && bms_is_member(dno, expr->paramnos)) { PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[dno]; if (var->dtype == PLPGSQL_DTYPE_VAR) { if (var->datatype->typoid == p->paramtype) { if (bms_is_member(dno, cstate->safe_variables)) return false; } } } } *location = p->location; return true; } } return false; } else return false; } #if PG_VERSION_NUM >= 90600 /* * These checker function returns volatility or immutability of any function. * Special case is plpgsql_check_pragma function. This function is vollatile, * but we should to fake flags to be immutable - becase we would not to change * result of analyzes and we know so this function has not any negative side * effect. */ static bool contain_volatile_functions_checker(Oid func_id, void *context) { PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; if (func_id == cstate->pragma_foid) return false; return (func_volatile(func_id) == PROVOLATILE_VOLATILE); } static bool contain_mutable_functions_checker(Oid func_id, void *context) { PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; if (func_id == cstate->pragma_foid) return false; return (func_volatile(func_id) != PROVOLATILE_IMMUTABLE); } static bool contain_volatile_functions_walker(Node *node, void *context) { if (node == NULL) return false; /* Check for volatile functions in node itself */ if (check_functions_in_node(node, contain_volatile_functions_checker, context)) return true; #if PG_VERSION_NUM >= 100000 if (IsA(node, NextValueExpr)) { /* NextValueExpr is volatile */ return true; } #endif /* * See notes in contain_mutable_functions_walker about why we treat * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while * SQLValueFunction is stable. Hence, none of them are of interest here. */ /* Recurse to check arguments */ if (IsA(node, Query)) { /* Recurse into subselects */ return query_tree_walker((Query *) node, contain_volatile_functions_walker, context, 0); } return expression_tree_walker(node, contain_volatile_functions_walker, context); } static bool contain_mutable_functions_walker(Node *node, void *context) { if (node == NULL) return false; /* Check for mutable functions in node itself */ if (check_functions_in_node(node, contain_mutable_functions_checker, context)) return true; #if PG_VERSION_NUM >= 100000 if (IsA(node, SQLValueFunction)) { /* all variants of SQLValueFunction are stable */ return true; } if (IsA(node, NextValueExpr)) { /* NextValueExpr is volatile */ return true; } #endif /* * It should be safe to treat MinMaxExpr as immutable, because it will * depend on a non-cross-type btree comparison function, and those should * always be immutable. Treating XmlExpr as immutable is more dubious, * and treating CoerceToDomain as immutable is outright dangerous. But we * have done so historically, and changing this would probably cause more * problems than it would fix. In practice, if you have a non-immutable * domain constraint you are in for pain anyhow. */ /* Recurse to check arguments */ if (IsA(node, Query)) { /* Recurse into subselects */ return query_tree_walker((Query *) node, contain_mutable_functions_walker, context, 0); } return expression_tree_walker(node, contain_mutable_functions_walker, context); } #else static bool contain_mutable_functions_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, FuncExpr)) { FuncExpr *expr = (FuncExpr *) node; PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; if (func_volatile(expr->funcid) != PROVOLATILE_IMMUTABLE && expr->funcid != cstate->pragma_foid) return true; /* else fall through to check args */ } else if (IsA(node, OpExpr)) { OpExpr *expr = (OpExpr *) node; set_opfuncid(expr); if (func_volatile(expr->opfuncid) != PROVOLATILE_IMMUTABLE) return true; /* else fall through to check args */ } else if (IsA(node, DistinctExpr)) { DistinctExpr *expr = (DistinctExpr *) node; set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ if (func_volatile(expr->opfuncid) != PROVOLATILE_IMMUTABLE) return true; /* else fall through to check args */ } else if (IsA(node, NullIfExpr)) { NullIfExpr *expr = (NullIfExpr *) node; set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ if (func_volatile(expr->opfuncid) != PROVOLATILE_IMMUTABLE) return true; /* else fall through to check args */ } else if (IsA(node, ScalarArrayOpExpr)) { ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; set_sa_opfuncid(expr); if (func_volatile(expr->opfuncid) != PROVOLATILE_IMMUTABLE) return true; /* else fall through to check args */ } else if (IsA(node, CoerceViaIO)) { CoerceViaIO *expr = (CoerceViaIO *) node; Oid iofunc; Oid typioparam; bool typisvarlena; /* check the result type's input function */ getTypeInputInfo(expr->resulttype, &iofunc, &typioparam); if (func_volatile(iofunc) != PROVOLATILE_IMMUTABLE) return true; /* check the input type's output function */ getTypeOutputInfo(exprType((Node *) expr->arg), &iofunc, &typisvarlena); if (func_volatile(iofunc) != PROVOLATILE_IMMUTABLE) return true; /* else fall through to check args */ } else if (IsA(node, ArrayCoerceExpr)) { ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node; if (OidIsValid(expr->elemfuncid) && func_volatile(expr->elemfuncid) != PROVOLATILE_IMMUTABLE) return true; /* else fall through to check args */ } else if (IsA(node, RowCompareExpr)) { RowCompareExpr *rcexpr = (RowCompareExpr *) node; ListCell *opid; foreach(opid, rcexpr->opnos) { if (op_volatile(lfirst_oid(opid)) != PROVOLATILE_IMMUTABLE) return true; } /* else fall through to check args */ } else if (IsA(node, Query)) { /* Recurse into subselects */ return query_tree_walker((Query *) node, contain_mutable_functions_walker, context, 0); } return expression_tree_walker(node, contain_mutable_functions_walker, context); } /* * General purpose code for checking expression volatility. * * Special purpose code for use in COPY is almost identical to this, * so any changes here may also be needed in other contain_volatile... * functions. */ static bool contain_volatile_functions_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, FuncExpr)) { FuncExpr *expr = (FuncExpr *) node; PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; if (func_volatile(expr->funcid) == PROVOLATILE_VOLATILE && expr->funcid != cstate->pragma_foid) return true; /* else fall through to check args */ } else if (IsA(node, OpExpr)) { OpExpr *expr = (OpExpr *) node; set_opfuncid(expr); if (func_volatile(expr->opfuncid) == PROVOLATILE_VOLATILE) return true; /* else fall through to check args */ } else if (IsA(node, DistinctExpr)) { DistinctExpr *expr = (DistinctExpr *) node; set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ if (func_volatile(expr->opfuncid) == PROVOLATILE_VOLATILE) return true; /* else fall through to check args */ } else if (IsA(node, NullIfExpr)) { NullIfExpr *expr = (NullIfExpr *) node; set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */ if (func_volatile(expr->opfuncid) == PROVOLATILE_VOLATILE) return true; /* else fall through to check args */ } else if (IsA(node, ScalarArrayOpExpr)) { ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; set_sa_opfuncid(expr); if (func_volatile(expr->opfuncid) == PROVOLATILE_VOLATILE) return true; /* else fall through to check args */ } else if (IsA(node, CoerceViaIO)) { CoerceViaIO *expr = (CoerceViaIO *) node; Oid iofunc; Oid typioparam; bool typisvarlena; /* check the result type's input function */ getTypeInputInfo(expr->resulttype, &iofunc, &typioparam); if (func_volatile(iofunc) == PROVOLATILE_VOLATILE) return true; /* check the input type's output function */ getTypeOutputInfo(exprType((Node *) expr->arg), &iofunc, &typisvarlena); if (func_volatile(iofunc) == PROVOLATILE_VOLATILE) return true; /* else fall through to check args */ } else if (IsA(node, ArrayCoerceExpr)) { ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node; if (OidIsValid(expr->elemfuncid) && func_volatile(expr->elemfuncid) == PROVOLATILE_VOLATILE) return true; /* else fall through to check args */ } else if (IsA(node, RowCompareExpr)) { /* RowCompare probably can't have volatile ops, but check anyway */ RowCompareExpr *rcexpr = (RowCompareExpr *) node; ListCell *opid; foreach(opid, rcexpr->opnos) { if (op_volatile(lfirst_oid(opid)) == PROVOLATILE_VOLATILE) return true; } /* else fall through to check args */ } else if (IsA(node, Query)) { /* Recurse into subselects */ return query_tree_walker((Query *) node, contain_volatile_functions_walker, context, 0); } return expression_tree_walker(node, contain_volatile_functions_walker, context); } #endif /* * This is same like Postgres buildin function, but it ignores used * plpgsql_check pragma function */ bool plpgsql_check_contain_volatile_functions(Node *clause, PLpgSQL_checkstate *cstate) { return contain_volatile_functions_walker(clause, cstate); } bool plpgsql_check_contain_mutable_functions(Node *clause, PLpgSQL_checkstate *cstate) { return contain_mutable_functions_walker(clause, cstate); } #if PG_VERSION_NUM >= 140000 static bool has_external_param_with_paramid(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Param)) { Param *p = (Param *) node; if (p->paramkind == PARAM_EXTERN && p->paramid > 0 && p->location != -1) { int dno = p->paramid - 1; if (dno == *((int *) context)) return true; } } return expression_tree_walker(node, has_external_param_with_paramid, context); } /* * This walker is used for checking usage target_param elsewhere than top subscripting * node. */ bool plpgsql_check_vardno_is_used_for_reading(Node *node, int dno) { if (node == NULL) return false; if (IsA(node, SubscriptingRef)) node = (Node *) ((SubscriptingRef *) node)->refassgnexpr; return has_external_param_with_paramid(node, (void *) &dno); } #endif