Bug 894669 - Add analysis for finding variables unnecessarily entrained by inner functions, r=luke.

This commit is contained in:
Brian Hackett 2013-07-16 18:54:47 -06:00
Родитель 4acf421a02
Коммит 7040cac9db
3 изменённых файлов: 193 добавлений и 0 удалений

Просмотреть файл

@ -172,6 +172,7 @@ static bool compileOnly = false;
static bool fuzzingSafe = false;
#ifdef DEBUG
static bool dumpEntrainedVariables = false;
static bool OOM_printAllocationCount = false;
#endif
@ -428,6 +429,12 @@ RunFile(JSContext *cx, Handle<JSObject*> obj, const char *filename, FILE *file,
RootedScript script(cx);
script = JS::Compile(cx, obj, options, file);
#ifdef DEBUG
if (dumpEntrainedVariables)
AnalyzeEntrainedVariables(cx, script);
#endif
JS_SetOptions(cx, oldopts);
JS_ASSERT_IF(!script, gGotError);
if (script && !compileOnly) {
@ -5069,6 +5076,11 @@ ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op)
#endif /* JS_ION */
#ifdef DEBUG
if (op->getBoolOption("dump-entrained-variables"))
dumpEntrainedVariables = true;
#endif
/* |scriptArgs| gets bound on the global before any code is run. */
if (!BindScriptArgs(cx, obj, op))
return EXIT_FAILURE;
@ -5302,6 +5314,10 @@ main(int argc, char **argv, char **envp)
"to test JIT codegen (no-op on platforms other than x86).")
|| !op.addBoolOption('\0', "fuzzing-safe", "Don't expose functions that aren't safe for "
"fuzzers to call")
#ifdef DEBUG
|| !op.addBoolOption('\0', "dump-entrained-variables", "Print variables which are "
"unnecessarily entrained by inner functions")
#endif
#ifdef JSGC_GENERATIONAL
|| !op.addBoolOption('\0', "no-ggc", "Disable Generational GC")
#endif

Просмотреть файл

@ -2192,3 +2192,175 @@ js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame)
ScopeIter si(frame, cx);
return GetDebugScope(cx, si);
}
#ifdef DEBUG
typedef HashSet<PropertyName *> PropertyNameSet;
static bool
RemoveReferencedNames(JSContext *cx, HandleScript script, PropertyNameSet &remainingNames)
{
// Remove from remainingNames --- the closure variables in some outer
// script --- any free variables in this script. This analysis isn't perfect:
//
// - It will not account for free variables in an inner script which are
// actually accessing some name in an intermediate script between the
// inner and outer scripts. This can cause remainingNames to be an
// underapproximation.
//
// - It will not account for new names introduced via eval. This can cause
// remainingNames to be an overapproximation. This would be easy to fix
// but is nice to have as the eval will probably not access these
// these names and putting eval in an inner script is bad news if you
// care about entraining variables unnecessarily.
for (jsbytecode *pc = script->code;
pc != script->code + script->length;
pc += GetBytecodeLength(pc))
{
PropertyName *name;
switch (JSOp(*pc)) {
case JSOP_NAME:
case JSOP_CALLNAME:
case JSOP_SETNAME:
name = script->getName(pc);
break;
case JSOP_GETALIASEDVAR:
case JSOP_CALLALIASEDVAR:
case JSOP_SETALIASEDVAR:
name = ScopeCoordinateName(cx, script, pc);
break;
default:
name = NULL;
break;
}
if (name)
remainingNames.remove(name);
}
if (script->hasObjects()) {
ObjectArray *objects = script->objects();
for (size_t i = 0; i < objects->length; i++) {
JSObject *obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
JSFunction *fun = &obj->as<JSFunction>();
RootedScript innerScript(cx, fun->getOrCreateScript(cx));
if (!innerScript)
return false;
if (!RemoveReferencedNames(cx, innerScript, remainingNames))
return false;
}
}
}
return true;
}
static bool
AnalyzeEntrainedVariablesInScript(JSContext *cx, HandleScript script, HandleScript innerScript)
{
PropertyNameSet remainingNames(cx);
if (!remainingNames.init())
return false;
for (BindingIter bi(script); bi; bi++) {
if (bi->aliased()) {
PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(bi->name());
if (!p && !remainingNames.add(p, bi->name()))
return false;
}
}
if (!RemoveReferencedNames(cx, innerScript, remainingNames))
return false;
if (!remainingNames.empty()) {
Sprinter buf(cx);
if (!buf.init())
return false;
buf.printf("Script ");
if (JSAtom *name = script->function()->displayAtom()) {
buf.putString(name);
buf.printf(" ");
}
buf.printf("(%s:%d) has variables entrained by ", script->filename(), script->lineno);
if (JSAtom *name = innerScript->function()->displayAtom()) {
buf.putString(name);
buf.printf(" ");
}
buf.printf("(%s:%d) ::", innerScript->filename(), innerScript->lineno);
for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); r.popFront()) {
buf.printf(" ");
buf.putString(r.front());
}
printf("%s\n", buf.string());
}
if (innerScript->hasObjects()) {
ObjectArray *objects = innerScript->objects();
for (size_t i = 0; i < objects->length; i++) {
JSObject *obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
JSFunction *fun = &obj->as<JSFunction>();
RootedScript innerInnerScript(cx, fun->nonLazyScript());
if (!AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript))
return false;
}
}
}
return true;
}
// Look for local variables in script or any other script inner to it, which are
// part of the script's call object and are unnecessarily entrained by their own
// inner scripts which do not refer to those variables. An example is:
//
// function foo() {
// var a, b;
// function bar() { return a; }
// function baz() { return b; }
// }
//
// |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|.
bool
js::AnalyzeEntrainedVariables(JSContext *cx, HandleScript script)
{
if (!script->hasObjects())
return true;
ObjectArray *objects = script->objects();
for (size_t i = 0; i < objects->length; i++) {
JSObject *obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
JSFunction *fun = &obj->as<JSFunction>();
RootedScript innerScript(cx, fun->getOrCreateScript(cx));
if (!innerScript)
return false;
if (script->function() && script->function()->isHeavyweight()) {
if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript))
return false;
}
if (!AnalyzeEntrainedVariables(cx, innerScript))
return false;
}
}
return true;
}
#endif

Просмотреть файл

@ -747,6 +747,11 @@ StaticBlockObject::enclosingBlock() const
return obj && obj->is<StaticBlockObject>() ? &obj->as<StaticBlockObject>() : NULL;
}
#ifdef DEBUG
bool
AnalyzeEntrainedVariables(JSContext *cx, HandleScript script);
#endif
} // namespace js
#endif /* vm_ScopeObject_h */