Esempio n. 1
0
/**
 * Perform strictness analysis for the specified supercombinator.
 *
 * The main thing this does is to call through to call through to check_strictness_r(). It also
 * compares the new argument usage information with the old, and sets *changed to true of there
 * is any difference.
 *
 * @param sc      The supercombinator being processed
 *
 * @param changed Pointer to an integer which will be set to true if there is any change to
 *                the strictness flags or usage information (thus necessitating another iteration)
 */
void check_strictness(scomb *sc, int *changed)
{
  list *used = NULL;
  int i;
  int *strictin;

  check_strictness_r(sc,sc->body,&used,changed);

  strictin = (int*)malloc(sc->nargs*sizeof(int));
  for (i = 0; i < sc->nargs; i++)
    strictin[i] = sc->strictin[i] || list_contains_string(used,sc->argnames[i]);

  if (memcmp(sc->strictin,strictin,sc->nargs*sizeof(int)))
    *changed = 1;

  free(sc->strictin);
  sc->strictin = strictin;
  list_free(used,NULL);
}
Esempio n. 2
0
/**
   Call env_set. If this is a path variable, e.g. PATH, validate the
   elements. On error, print a description of the problem to stderr.
*/
static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope)
{
    size_t i;
    int retcode = 0;
    const wchar_t *val_str=NULL;

    if (is_path_variable(key))
    {
        /* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */
        bool any_success = false;

        /* Don't bother validating (or complaining about) values that are already present */
        wcstring_list_t existing_values;
        const env_var_t existing_variable = env_get_string(key, scope);
        if (! existing_variable.missing_or_empty())
            tokenize_variable_array(existing_variable, existing_values);

        for (i=0; i< val.size() ; i++)
        {
            const wcstring &dir = val.at(i);
            if (list_contains_string(existing_values, dir))
            {
                any_success = true;
                continue;
            }

            bool show_perror = false;
            int show_hint = 0;
            bool error = false;

            struct stat buff;
            if (wstat(dir, &buff))
            {
                error = true;
                show_perror = true;
            }

            if (!(S_ISDIR(buff.st_mode)))
            {
                error = true;
            }

            if (!error)
            {
                any_success = true;
            }
            else
            {
                append_format(stderr_buffer, _(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key);
                const wchar_t *colon = wcschr(dir.c_str(), L':');

                if (colon && *(colon+1))
                {
                    show_hint = 1;
                }

            }

            if (show_perror)
            {
                builtin_wperror(L"set");
            }

            if (show_hint)
            {
                append_format(stderr_buffer, _(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1);
            }

        }

        /* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */
        if (! val.empty() && ! any_success)
        {
            return 1;
        }

    }

    wcstring sb;
    if (! val.empty())
    {
        for (i=0; i< val.size() ; i++)
        {
            sb.append(val[i]);
            if (i<val.size() - 1)
            {
                sb.append(ARRAY_SEP_STR);
            }
        }
        val_str = sb.c_str();
    }

    switch (env_set(key, val_str, scope | ENV_USER))
    {
        case ENV_PERM:
        {
            append_format(stderr_buffer, _(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key);
            retcode=1;
            break;
        }

        case ENV_SCOPE:
        {
            append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' with the wrong scope\n"), L"set", key);
            retcode=1;
            break;
        }

        case ENV_INVALID:
        {
            append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key);
            retcode=1;
            break;
        }
    }

    return retcode;
}
Esempio n. 3
0
/**
 * Recursively perform strictness analysis on an expression.
 *
 * This function does three things:
 * - Records which arguments will definitely be evaluated, assuming this expression is evaluated
 * - Marks any application nodes corresponding to an expression being passed as an argument to
 *   a supercombinator or built-in function which is strict in that argument
 * - Marks any letrec bindings used within the expression as strict
 *
 * @param sc      The supercombinator being processed
 *
 * @param c       The expression to analysed
 *
 * @param used    A (sorted) list of strings indicating the variables that are used in a strict
 *                context within the expression. This list is updated by the function if any new
 *                ones are encountered.
 *
 * @param changed A pointer to an integer that is set to true if a change was made to the
 *                strictness flag of one or more application nodes. Changes to used are not
 *                recorded here; this is the responsibility of check_strictness().
 */
static void check_strictness_r(scomb *sc, snode *c, list **used, int *changed)
{
  switch (c->type) {
  case SNODE_LETREC: {
    letrec *rec;
    list *bodyused = NULL;
    list *l;

    int again;
    do {
      again = 0;
      for (rec = c->bindings; rec; rec = rec->next)
        if (rec->strict)
          check_strictness_r(sc,rec->value,&bodyused,changed);
      check_strictness_r(sc,c->body,&bodyused,changed);

      for (rec = c->bindings; rec; rec = rec->next) {
        if (!rec->strict && list_contains_string(bodyused,rec->name)) {
          rec->strict = 1;
          again = 1;
          *changed = 1;
        }
      }
    } while (again);

    for (l = bodyused; l; l = l->next) {
      int isrec = 0;
      for (rec = c->bindings; rec && !isrec; rec = rec->next)
        if (!strcmp((char*)l->data,rec->name))
          isrec = 1;
      if (!isrec) {
        add_var(used,(char*)l->data);
      }
    }
    list_free(bodyused,NULL);
    break;
  }
  case SNODE_APPLICATION: {
    snode *fun;
    int nargs = 0;
    for (fun = c; SNODE_APPLICATION == fun->type; fun = fun->left)
      nargs++;

    /* We have discovered the item at the bottom of the spine, which is the function to be called.
       However we can only perform strictness analysis if we know statically what that function
       is - i.e. if the node is a supercombinator reference of built-in function.

       It is also possible that it could be a symbol, corresponding to a higher-order function
       passed in as an argument, but since we don't know anything about it we skip this case.

       Additionally, we will wait until we have an application to the right number of arguments
       before doing the analysis - if this is not the case yet we'll just do a recursive call
       to the next item in the application chain and handle it later. */
    if (((SNODE_SCREF == fun->type) || (SNODE_BUILTIN == fun->type)) &&
        (nargs == fun_nargs(fun))) {

      /* Follow the left branches down the tree, inspecting each argument and treating it as
         strict or not depending on what we know about the appropriate argument to the function. */
      snode *app = c;
      int argno;
      for (argno = nargs-1; 0 <= argno; argno--) {

        /* If the function is strict in this argument, mark the application node as strict.
           This will provide a hint to the bytecode compiler that it may compile the expression
           directly instead of adding MKAP instructions to create application nodes at runtime. */
        if (fun_strictin(fun,argno)) {
          *changed = (*changed || !app->strict);
          app->strict = 1;

          /* The expression will definitely need to be evaluated, i.e. it is in a strictness
             context. Perform the analysis recursively. */
          check_strictness_r(sc,app->right,used,changed);
        }

        app = app->left;
      }

      /* If statements need special treatment. The first argument (i.e. the conditional) is strict
         since this is always evaluated. But the second and third arguments (i.e. true and false)
         branches are not strict, since we don't know which will be needed until after the
         conditional is evaluated at runtime. A variable is only considered strict on the branches
         of an if in the case where it is used in a strict context in *both* branches.

         Despite the 2nd and 3rd arguments to if not being strict, we treat the *contents* of these
         expressions as being so. Whichever branch is taken will definitely need to return a value,
         so they are treated as a strict context and analysed with another recursive call to
         check_strictness_r(). This information is still relevant to the bytecode compiler due to
         the optimised way in which it compiles if statements using JFALSE and JUMP instructions.
         We also annotate application nodes within the true/false branches with strictness
         where appropriate. */
      if ((SNODE_BUILTIN == fun->type) && (B_IF == fun->bif)) {
        snode *falsebranch = c->right;
        snode *truebranch = c->left->right;

        list *trueused = NULL;
        list *falseused = NULL;

        check_strictness_r(sc,truebranch,&trueused,changed);
        check_strictness_r(sc,falsebranch,&falseused,changed);

        /* Merge the argument usage information from both branches, keeping only those arguments
           which appear in both. */
        add_union(used,trueused,falseused);
        list_free(trueused,NULL);
        list_free(falseused,NULL);
      }
      /* As with the true/false branches of an if call, we can also treat the contents of the
         second argument to seq as strict. The bytecode compiler also contains an optimisation
         for seq where it will evaluate the first argument, discard the result, and then evaluate
         the second argument. So we know that any function applications within the second argument
         will definitely be needed. However, we do not add variables within the expression to our
         used list, as we need to ensure that things referenced by the second argument are not
         evaluated until *after* the first argument (like with if). */
      if ((SNODE_BUILTIN == fun->type) && (B_SEQ == fun->bif)) {
        snode *after = c->right;
        list *afterused = NULL;
        check_strictness_r(sc,after,&afterused,changed);
        list_free(afterused,NULL);
      }
    }

    /* The expression representing the thing being called is in a strict context, as we definitely
       need the function. */
    check_strictness_r(sc,c->left,used,changed);
    break;
  }
  case SNODE_SYMBOL:
    /* We are in a strict context and have an encountered a symbol, which must correspond to
       one of the supercombinator's arguments or a letrec binding. Add the variable to the list
       to indicate that this argument will definitely be evaluated. */
    add_var(used,c->name);
    break;
  case SNODE_BUILTIN:
  case SNODE_SCREF:
  case SNODE_NIL:
  case SNODE_NUMBER:
  case SNODE_STRING:
    break;
  default:
    abort();
    break;
  }
}
Esempio n. 4
0
/**
   Call env_set. If this is a path variable, e.g. PATH, validate the
   elements. On error, print a description of the problem to stderr.
*/
static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, io_streams_t &streams)
{
    size_t i;
    int retcode = 0;
    const wchar_t *val_str=NULL;

    if (is_path_variable(key))
    {
        /* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */
        bool any_success = false;

        /* Don't bother validating (or complaining about) values that are already present.
           When determining already-present values, use ENV_DEFAULT instead of the passed-in scope because in:
              set -l PATH stuff $PATH
           where we are temporarily shadowing a variable, we want to compare against the shadowed value, not the
           (missing) local value.
           Also don't bother to complain about relative paths, which don't start with /.
        */
        wcstring_list_t existing_values;
        const env_var_t existing_variable = env_get_string(key, ENV_DEFAULT);
        if (! existing_variable.missing_or_empty())
            tokenize_variable_array(existing_variable, existing_values);

        for (i=0; i< val.size() ; i++)
        {
            const wcstring &dir = val.at(i);
            if (!string_prefixes_string(L"/", dir) || list_contains_string(existing_values, dir))
            {
                any_success = true;
                continue;
            }

            bool show_perror = false;
            int show_hint = 0;
            bool error = false;

            struct stat buff;
            if (wstat(dir, &buff))
            {
                error = true;
                show_perror = true;
            }

            if (!(S_ISDIR(buff.st_mode)))
            {
                error = true;
            }

            if (!error)
            {
                any_success = true;
            }
            else
            {
                streams.err.append_format(_(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key);
                const wchar_t *colon = wcschr(dir.c_str(), L':');

                if (colon && *(colon+1))
                {
                    show_hint = 1;
                }

            }

            if (show_perror)
            {
                builtin_wperror(L"set", streams);
            }

            if (show_hint)
            {
                streams.err.append_format(_(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1);
            }

        }

        /* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */
        if (! val.empty() && ! any_success)
        {
            return 1;
        }

    }

    wcstring sb;
    if (! val.empty())
    {
        for (i=0; i< val.size() ; i++)
        {
            sb.append(val[i]);
            if (i<val.size() - 1)
            {
                sb.append(ARRAY_SEP_STR);
            }
        }
        val_str = sb.c_str();
    }

    switch (env_set(key, val_str, scope | ENV_USER))
    {
        case ENV_PERM:
        {
            streams.err.append_format(_(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key);
            retcode=1;
            break;
        }

        case ENV_SCOPE:
        {
            streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' with the wrong scope\n"), L"set", key);
            retcode=1;
            break;
        }

        case ENV_INVALID:
        {
            streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key);
            retcode=1;
            break;
        }
    }

    return retcode;
}