Fix JS DCE removing reachable functions

This commit is contained in:
Alexey Andreev
2017-10-09 20:12:03 +03:00
parent cb7d561c6c
commit 8cdef4139e
11 changed files with 108 additions and 13 deletions
@@ -60,7 +60,7 @@ class K2JSDce : CLITool<K2JSDceArguments>() {
} }
val dceResult = DeadCodeElimination.run(files, includedDeclarations, logConsumer) val dceResult = DeadCodeElimination.run(files, includedDeclarations, logConsumer)
if (dceResult.status == DeadCodeEliminationStatus.FAILED) return ExitCode.COMPILATION_ERROR if (dceResult.status == DeadCodeEliminationStatus.FAILED) return ExitCode.COMPILATION_ERROR
val nodes = dceResult.reachableNodes val nodes = dceResult.reachableNodes.filterTo(mutableSetOf()) { it.reachable }
val reachabilitySeverity = if (arguments.printReachabilityInfo) CompilerMessageSeverity.INFO else CompilerMessageSeverity.LOGGING val reachabilitySeverity = if (arguments.printReachabilityInfo) CompilerMessageSeverity.INFO else CompilerMessageSeverity.LOGGING
messageCollector.report(reachabilitySeverity, "") messageCollector.report(reachabilitySeverity, "")
@@ -57,9 +57,21 @@ class ReachabilityTracker(
analysisResult.nodeMap[x] == null && x !in analysisResult.astNodesToEliminate analysisResult.nodeMap[x] == null && x !in analysisResult.astNodesToEliminate
override fun visitNameRef(nameRef: JsNameRef) { override fun visitNameRef(nameRef: JsNameRef) {
if (nameRef in analysisResult.astNodesToSkip) return if (visitNameLikeNode(nameRef)) {
super.visitNameRef(nameRef)
}
}
val node = context.extractNode(nameRef) override fun visitArrayAccess(x: JsArrayAccess) {
if (visitNameLikeNode(x)) {
super.visitArrayAccess(x)
}
}
private fun visitNameLikeNode(x: JsExpression): Boolean {
if (x in analysisResult.astNodesToSkip) return false
val node = context.extractNode(x)
if (node != null) { if (node != null) {
if (!node.reachable) { if (!node.reachable) {
reportAndNest("reach: referenced name $node", currentNodeWithLocation) { reportAndNest("reach: referenced name $node", currentNodeWithLocation) {
@@ -67,9 +79,10 @@ class ReachabilityTracker(
currentNodeWithLocation?.let { node.usedByAstNodes += it } currentNodeWithLocation?.let { node.usedByAstNodes += it }
} }
} }
return false
} }
else { else {
super.visitNameRef(nameRef) return true
} }
} }
@@ -179,6 +192,7 @@ class ReachabilityTracker(
} }
else if (!node.declarationReachable) { else if (!node.declarationReachable) {
node.declarationReachable = true node.declarationReachable = true
reachableNodesImpl += node
node.original.qualifier?.parent?.let { node.original.qualifier?.parent?.let {
reportAndNest("reach-decl: parent $it", null) { reportAndNest("reach-decl: parent $it", null) {
@@ -197,8 +211,8 @@ class ReachabilityTracker(
override fun visitPrefixOperation(x: JsPrefixOperation) { override fun visitPrefixOperation(x: JsPrefixOperation) {
if (x.operator == JsUnaryOperator.TYPEOF) { if (x.operator == JsUnaryOperator.TYPEOF) {
val arg = x.arg val arg = x.arg
if (arg is JsNameRef && arg.qualifier == null) { context.extractNode(arg)?.let {
context.extractNode(arg)?.let { reachDeclaration(it) } reachDeclaration(it)
return return
} }
} }
@@ -562,15 +562,18 @@ public class JsAstMapper {
Node fromFnNameNode = fnNode.getFirstChild(); Node fromFnNameNode = fnNode.getFirstChild();
Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild(); Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild();
Node fromBodyNode = fnNode.getFirstChild().getNext().getNext(); Node fromBodyNode = fnNode.getFirstChild().getNext().getNext();
JsFunction toFn = scopeContext.enterFunction();
// Decide the function's name, if any. // Decide the function's name, if any.
// //
String fnNameIdent = fromFnNameNode.getString(); String fnNameIdent = fromFnNameNode.getString();
JsName functionName = null;
if (fnNameIdent != null && fnNameIdent.length() > 0) { if (fnNameIdent != null && fnNameIdent.length() > 0) {
toFn.setName(scopeContext.globalNameFor(fnNameIdent)); functionName = scopeContext.localNameFor(fnNameIdent);
} }
JsFunction toFn = scopeContext.enterFunction();
toFn.setName(functionName);
while (fromParamNode != null) { while (fromParamNode != null) {
String fromParamName = fromParamNode.getString(); String fromParamName = fromParamNode.getString();
JsName name = scopeContext.localNameFor(fromParamName); JsName name = scopeContext.localNameFor(fromParamName);
@@ -542,13 +542,14 @@ abstract class BasicBoxTest(
val testFunctionFqn = testModuleName + (if (testPackage.isNullOrEmpty()) "" else ".$testPackage") + ".$testFunction" val testFunctionFqn = testModuleName + (if (testPackage.isNullOrEmpty()) "" else ".$testPackage") + ".$testFunction"
val additionalReachableNodes = setOf( val additionalReachableNodes = setOf(
testFunctionFqn, "kotlin.kotlin.io.BufferedOutput", "kotlin.kotlin.io.output.flush", testFunctionFqn, "kotlin.kotlin.io.BufferedOutput", "kotlin.kotlin.io.output.flush",
"kotlin.kotlin.io.output.buffer", "kotlin-test.kotlin.test.overrideAsserter_wbnzx$" "kotlin.kotlin.io.output.buffer", "kotlin-test.kotlin.test.overrideAsserter_wbnzx$",
"kotlin-test.kotlin.test.DefaultAsserter"
) )
val allFilesToMinify = filesToMinify.values + kotlinJsInputFile + kotlinTestJsInputFile val allFilesToMinify = filesToMinify.values + kotlinJsInputFile + kotlinTestJsInputFile
val dceResult = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes) { _, _ -> } val dceResult = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes) { _, _ -> }
val reachableNodes = dceResult.reachableNodes val reachableNodes = dceResult.reachableNodes
minificationThresholdChecker(reachableNodes.size) minificationThresholdChecker(reachableNodes.count { it.reachable })
val runList = mutableListOf<String>() val runList = mutableListOf<String>()
runList += kotlinJsLibOutput runList += kotlinJsLibOutput
@@ -42,6 +42,12 @@ public class DceTestGenerated extends AbstractDceTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("arrayAccess.js")
public void testArrayAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/dce/arrayAccess.js");
doTest(fileName);
}
@TestMetadata("commonjs.js") @TestMetadata("commonjs.js")
public void testCommonjs() throws Exception { public void testCommonjs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/dce/commonjs.js"); String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/dce/commonjs.js");
@@ -53,4 +59,16 @@ public class DceTestGenerated extends AbstractDceTest {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/dce/cycle.js"); String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/dce/cycle.js");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("localVarAndFunction.js")
public void testLocalVarAndFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/dce/localVarAndFunction.js");
doTest(fileName);
}
@TestMetadata("typeOf.js")
public void testTypeOf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/dce/typeOf.js");
doTest(fileName);
}
} }
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 1230 // EXPECTED_REACHABLE_NODES: 1106
package foo package foo
class SimpleEnumerator { class SimpleEnumerator {
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 1222 // EXPECTED_REACHABLE_NODES: 1098
package foo package foo
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 1249 // EXPECTED_REACHABLE_NODES: 1123
package foo package foo
class C(val i: Int) : Comparable<C>, A() { class C(val i: Int) : Comparable<C>, A() {
+17
View File
@@ -0,0 +1,17 @@
(function(_) {
var x = {};
function foo() {
return x["bar"]();
}
x.bar = function() {
return "bar";
};
x.baz = function() {
return "baz";
};
_.foo = foo;
})(module.exports);
// REQUEST_REACHABLE: main.foo
// ASSERT_REACHABLE: x.bar
// ASSERT_UNREACHABLE: x.baz
+27
View File
@@ -0,0 +1,27 @@
(function(_, Kotlin) {
Kotlin.defineInlineFunction("bar", Kotlin.wrapFunction(function() {
function baz() {
return "bar";
}
return function() {
return baz();
};
}));
return _;
})(module.exports, require("kotlin"));
(function(_) {
function baz() {
return "baz";
}
function foo() {
return baz();
}
_.foo = foo;
})(module.exports);
// REQUEST_REACHABLE: main.foo
// ASSERT_REACHABLE: main.foo
// ASSERT_REACHABLE: baz
+15
View File
@@ -0,0 +1,15 @@
(function(_) {
var x = {};
x.y = {};
x.y.bar = function() {
return "bar";
};
function foo() {
return typeof x.y;
}
_.foo = foo;
})(module.exports);
// REQUEST_REACHABLE: main.foo
// ASSERT_REACHABLE: x.y
// ASSERT_UNREACHABLE: x.y.bar