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)
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
messageCollector.report(reachabilitySeverity, "")
@@ -57,9 +57,21 @@ class ReachabilityTracker(
analysisResult.nodeMap[x] == null && x !in analysisResult.astNodesToEliminate
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.reachable) {
reportAndNest("reach: referenced name $node", currentNodeWithLocation) {
@@ -67,9 +79,10 @@ class ReachabilityTracker(
currentNodeWithLocation?.let { node.usedByAstNodes += it }
}
}
return false
}
else {
super.visitNameRef(nameRef)
return true
}
}
@@ -179,6 +192,7 @@ class ReachabilityTracker(
}
else if (!node.declarationReachable) {
node.declarationReachable = true
reachableNodesImpl += node
node.original.qualifier?.parent?.let {
reportAndNest("reach-decl: parent $it", null) {
@@ -197,8 +211,8 @@ class ReachabilityTracker(
override fun visitPrefixOperation(x: JsPrefixOperation) {
if (x.operator == JsUnaryOperator.TYPEOF) {
val arg = x.arg
if (arg is JsNameRef && arg.qualifier == null) {
context.extractNode(arg)?.let { reachDeclaration(it) }
context.extractNode(arg)?.let {
reachDeclaration(it)
return
}
}
@@ -562,15 +562,18 @@ public class JsAstMapper {
Node fromFnNameNode = fnNode.getFirstChild();
Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild();
Node fromBodyNode = fnNode.getFirstChild().getNext().getNext();
JsFunction toFn = scopeContext.enterFunction();
// Decide the function's name, if any.
//
String fnNameIdent = fromFnNameNode.getString();
JsName functionName = null;
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) {
String fromParamName = fromParamNode.getString();
JsName name = scopeContext.localNameFor(fromParamName);
@@ -542,13 +542,14 @@ abstract class BasicBoxTest(
val testFunctionFqn = testModuleName + (if (testPackage.isNullOrEmpty()) "" else ".$testPackage") + ".$testFunction"
val additionalReachableNodes = setOf(
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 dceResult = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes) { _, _ -> }
val reachableNodes = dceResult.reachableNodes
minificationThresholdChecker(reachableNodes.size)
minificationThresholdChecker(reachableNodes.count { it.reachable })
val runList = mutableListOf<String>()
runList += kotlinJsLibOutput
@@ -42,6 +42,12 @@ public class DceTestGenerated extends AbstractDceTest {
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")
public void testCommonjs() throws Exception {
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");
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
class SimpleEnumerator {
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 1222
// EXPECTED_REACHABLE_NODES: 1098
package foo
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 1249
// EXPECTED_REACHABLE_NODES: 1123
package foo
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