| Code with Finding: |
class CrossModuleMethodMotion {
/**
* Move methods deeper in the module graph when possible.
*/
private void moveMethods(Collection<NameInfo> allNameInfo) {
boolean hasStubDeclaration = idGenerator.hasGeneratedAnyIds();
for (NameInfo nameInfo : allNameInfo) {
if (!nameInfo.isReferenced()) {
// The code below can't do anything with unreferenced name
// infos. They should be skipped to avoid NPE since their
// deepestCommonModuleRef is null.
continue;
}
if (nameInfo.readsClosureVariables()) {
continue;
}
JSModule deepestCommonModuleRef = nameInfo.getDeepestCommonModuleRef();
if(deepestCommonModuleRef == null) {
compiler.report(JSError.make(NULL_COMMON_MODULE_ERROR));
continue;
}
Iterator<Symbol> declarations =
nameInfo.getDeclarations().descendingIterator();
while (declarations.hasNext()) {
Symbol symbol = declarations.next();
if (!(symbol instanceof Property)) {
continue;
}
Property prop = (Property) symbol;
// We should only move a property across modules if:
// 1) We can move it deeper in the module graph, and
// 2) it's a function.
// 3) it is not a get or a set.
//
// #1 should be obvious. #2 is more subtle. It's possible
// to copy off of a prototype, as in the code:
// for (var k in Foo.prototype) {
// doSomethingWith(Foo.prototype[k]);
// }
// This is a common way to implement pseudo-multiple inheritance in JS.
//
// So if we move a prototype method into a deeper module, we must
// replace it with a stub function so that it preserves its original
// behavior.
Node value = prop.getValue();
if (moduleGraph.dependsOn(deepestCommonModuleRef, prop.getModule()) &&
value.getType() == Token.FUNCTION) {
Node valueParent = value.getParent();
if (valueParent.getType() == Token.GET
|| valueParent.getType() == Token.SET) {
// TODO(johnlenz): a GET or SET can't be deferred like a normal
// FUNCTION property definition as a mix-in would get the result
// of a GET instead of the function itself.
continue;
}
Node proto = prop.getPrototype();
int stubId = idGenerator.newId();
// stub out the method in the original module
valueParent.replaceChild(value,
// A.prototype.b = JSCompiler_stubMethod(id);
new Node(Token.CALL,
Node.newString(Token.NAME, STUB_METHOD_NAME),
Node.newNumber(stubId))
.copyInformationFromForTree(value));
// unstub the function body in the deeper module
Node unstubParent = compiler.getNodeForCodeInsertion(
deepestCommonModuleRef);
unstubParent.addChildToFront(
// A.prototype.b = JSCompiler_unstubMethod(id, body);
new Node(Token.EXPR_RESULT,
new Node(Token.ASSIGN,
new Node(Token.GETPROP,
proto.cloneTree(),
Node.newString(Token.STRING, nameInfo.name)),
new Node(Token.CALL,
Node.newString(Token.NAME, UNSTUB_METHOD_NAME),
Node.newNumber(stubId),
value)))
.copyInformationFromForTree(value));
compiler.reportCodeChange();
logger.fine("Moved method: " +
proto.getQualifiedName() + "." + nameInfo.name +
" from module " + prop.getModule() + " to module " +
deepestCommonModuleRef);
}
}
}
if (!hasStubDeclaration && idGenerator.hasGeneratedAnyIds()) {
// Declare stub functions in the top-most module.
Node declarations = compiler.parseSyntheticCode(STUB_DECLARATIONS);
compiler.getNodeForCodeInsertion(null).addChildrenToFront(
declarations.removeChildren());
}
}
}
|