Make function calls show up in stack traces again

Note that adding --show-trace prevents functions calls from being
tail-recursive, so an expression that evaluates without --show-trace
may fail with a stack overflow if --show-trace is given.
This commit is contained in:
Eelco Dolstra 2013-11-12 12:51:59 +01:00
parent 2bcb384e95
commit 89e6781cc5
4 changed files with 34 additions and 25 deletions

View File

@ -279,6 +279,11 @@ LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2))
e.addPrefix(format(s) % s2); e.addPrefix(format(s) % s2);
} }
LocalNoInline(void addErrorPrefix(Error & e, const char * s, const ExprLambda & fun))
{
e.addPrefix(format(s) % fun.showNamePos());
}
LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2, const Pos & pos)) LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2, const Pos & pos))
{ {
e.addPrefix(format(s) % s2 % pos); e.addPrefix(format(s) % s2 % pos);
@ -757,32 +762,34 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v)
if (fun.type != tLambda) if (fun.type != tLambda)
throwTypeError("attempt to call something which is not a function but %1%", fun); throwTypeError("attempt to call something which is not a function but %1%", fun);
ExprLambda & lambda(*fun.lambda.fun);
unsigned int size = unsigned int size =
(fun.lambda.fun->arg.empty() ? 0 : 1) + (lambda.arg.empty() ? 0 : 1) +
(fun.lambda.fun->matchAttrs ? fun.lambda.fun->formals->formals.size() : 0); (lambda.matchAttrs ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size)); Env & env2(allocEnv(size));
env2.up = fun.lambda.env; env2.up = fun.lambda.env;
unsigned int displ = 0; unsigned int displ = 0;
if (!fun.lambda.fun->matchAttrs) if (!lambda.matchAttrs)
env2.values[displ++] = &arg; env2.values[displ++] = &arg;
else { else {
forceAttrs(arg); forceAttrs(arg);
if (!fun.lambda.fun->arg.empty()) if (!lambda.arg.empty())
env2.values[displ++] = &arg; env2.values[displ++] = &arg;
/* For each formal argument, get the actual argument. If /* For each formal argument, get the actual argument. If
there is no matching actual argument but the formal there is no matching actual argument but the formal
argument has a default, use the default. */ argument has a default, use the default. */
unsigned int attrsUsed = 0; unsigned int attrsUsed = 0;
foreach (Formals::Formals_::iterator, i, fun.lambda.fun->formals->formals) { foreach (Formals::Formals_::iterator, i, lambda.formals->formals) {
Bindings::iterator j = arg.attrs->find(i->name); Bindings::iterator j = arg.attrs->find(i->name);
if (j == arg.attrs->end()) { if (j == arg.attrs->end()) {
if (!i->def) throwTypeError("%1% called without required argument `%2%'", if (!i->def) throwTypeError("%1% called without required argument `%2%'",
*fun.lambda.fun, i->name); lambda, i->name);
env2.values[displ++] = i->def->maybeThunk(*this, env2); env2.values[displ++] = i->def->maybeThunk(*this, env2);
} else { } else {
attrsUsed++; attrsUsed++;
@ -792,29 +799,30 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v)
/* Check that each actual argument is listed as a formal /* Check that each actual argument is listed as a formal
argument (unless the attribute match specifies a `...'). */ argument (unless the attribute match specifies a `...'). */
if (!fun.lambda.fun->formals->ellipsis && attrsUsed != arg.attrs->size()) { if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) {
/* Nope, so show the first unexpected argument to the /* Nope, so show the first unexpected argument to the
user. */ user. */
foreach (Bindings::iterator, i, *arg.attrs) foreach (Bindings::iterator, i, *arg.attrs)
if (fun.lambda.fun->formals->argNames.find(i->name) == fun.lambda.fun->formals->argNames.end()) if (lambda.formals->argNames.find(i->name) == lambda.formals->argNames.end())
throwTypeError("%1% called with unexpected argument `%2%'", *fun.lambda.fun, i->name); throwTypeError("%1% called with unexpected argument `%2%'", lambda, i->name);
abort(); // can't happen abort(); // can't happen
} }
} }
nrFunctionCalls++; nrFunctionCalls++;
if (countCalls) incrFunctionCall(fun.lambda.fun); if (countCalls) incrFunctionCall(&lambda);
fun.lambda.fun->body->eval(*this, env2, v); /* Evaluate the body. This is conditional on showTrace, because
catching exceptions makes this function not tail-recursive. */
#if 0 if (settings.showTrace)
try { try {
lambda.body->eval(*this, env2, v);
} catch (Error & e) {
addErrorPrefix(e, "while evaluating %1%:\n", lambda);
throw;
}
else
fun.lambda.fun->body->eval(*this, env2, v); fun.lambda.fun->body->eval(*this, env2, v);
} catch (Error & e) {
addErrorPrefix(e, "while evaluating %1%:\n", fun.lambda.fun->showNamePos());
throw;
}
#endif
} }

View File

@ -89,9 +89,6 @@ static void setLogType(string lt)
} }
static bool showTrace = false;
string getArg(const string & opt, string getArg(const string & opt,
Strings::iterator & i, const Strings::iterator & end) Strings::iterator & i, const Strings::iterator & end)
{ {
@ -214,7 +211,7 @@ static void initAndRun(int argc, char * * argv)
else if (arg == "--no-build-hook") else if (arg == "--no-build-hook")
settings.useBuildHook = false; settings.useBuildHook = false;
else if (arg == "--show-trace") else if (arg == "--show-trace")
showTrace = true; settings.showTrace = true;
else if (arg == "--option") { else if (arg == "--option") {
++i; if (i == args.end()) throw UsageError("`--option' requires two arguments"); ++i; if (i == args.end()) throw UsageError("`--option' requires two arguments");
string name = *i; string name = *i;
@ -299,8 +296,8 @@ int main(int argc, char * * argv)
% e.what() % programId); % e.what() % programId);
return 1; return 1;
} catch (BaseError & e) { } catch (BaseError & e) {
printMsg(lvlError, format("error: %1%%2%") % (showTrace ? e.prefix() : "") % e.msg()); printMsg(lvlError, format("error: %1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
if (e.prefix() != "" && !showTrace) if (e.prefix() != "" && !settings.showTrace)
printMsg(lvlError, "(use `--show-trace' to show detailed location information)"); printMsg(lvlError, "(use `--show-trace' to show detailed location information)");
return e.status; return e.status;
} catch (std::bad_alloc & e) { } catch (std::bad_alloc & e) {

View File

@ -55,6 +55,7 @@ Settings::Settings()
autoOptimiseStore = false; autoOptimiseStore = false;
envKeepDerivations = false; envKeepDerivations = false;
lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1"; lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
showTrace = false;
} }

View File

@ -186,6 +186,9 @@ struct Settings {
/* Whether to lock the Nix client and worker to the same CPU. */ /* Whether to lock the Nix client and worker to the same CPU. */
bool lockCPU; bool lockCPU;
/* Whether to show a stack trace if Nix evaluation fails. */
bool showTrace;
private: private:
SettingsMap settings, overrides; SettingsMap settings, overrides;