JS: validate AST when serializing to IC cache

This commit is contained in:
Anton Bannykh
2018-12-21 18:58:11 +03:00
parent e4b081e1bf
commit 299a45ccbf
6 changed files with 54 additions and 28 deletions
@@ -40,7 +40,7 @@ abstract class AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation : Abstra
val diagnostics = analysisResult.bindingTrace.bindingContext.diagnostics
if (!hasError(diagnostics)) {
val translator = K2JSTranslator(config)
val translator = K2JSTranslator(config, true)
translator.translate(object : JsConfig.Reporter() {}, files, MainCallParameters.noCall(), analysisResult)
}
@@ -107,6 +107,11 @@ fun collectDefinedNames(scope: JsNode): Set<JsName> {
super.visitExpressionStatement(x)
}
override fun visitBreak(x: JsBreak) {
x.label?.name?.let { names += it }
super.visitBreak(x)
}
override fun visitCatch(x: JsCatch) {
names += x.parameter.name
super.visitCatch(x)
@@ -134,6 +139,16 @@ fun collectDefinedNamesInAllScopes(scope: JsNode): Set<JsName> {
x.name?.let { names += it }
names += x.parameters.map { it.name }
}
override fun visitBreak(x: JsBreak) {
x.label?.name?.let { names += it }
super.visitBreak(x)
}
override fun visitCatch(x: JsCatch) {
names += x.parameter.name
super.visitCatch(x)
}
}.accept(scope)
return names
@@ -238,26 +253,6 @@ fun extractFunction(expression: JsExpression) = when (expression) {
else -> InlineMetadata.decompose(expression)?.function ?: InlineMetadata.tryExtractFunction(expression)
}
fun collectInlineFunctionTags(config: JsConfig, fragments: List<JsProgramFragment>): Set<String> {
val result = mutableSetOf<String>()
for (fragment in fragments) {
collectInlineFunctionTags(config, fragment.declarationBlock, result)
collectInlineFunctionTags(config, fragment.initializerBlock, result)
}
return result
}
private fun collectInlineFunctionTags(config: JsConfig, scope: JsNode, result: MutableSet<String>) {
scope.accept(object : RecursiveJsVisitor() {
override fun visitInvocation(invocation: JsInvocation) {
invocation.descriptor?.let {
result.add(Namer.getFunctionTag(it, config))
}
super.visitInvocation(invocation)
}
})
}
fun <T : JsNode> collectInstances(klass: Class<T>, scope: JsNode): List<T> {
return with(InstanceCollector(klass, visitNestedDeclarations = false)) {
accept(scope)
@@ -30,7 +30,8 @@ import java.io.OutputStream
import java.util.*
import org.jetbrains.kotlin.resolve.inline.InlineStrategy as KotlinInlineStrategy
class JsAstSerializer(private val pathResolver: (File) -> String) {
class JsAstSerializer(private val jsAstValidator: ((JsProgramFragment, Set<JsName>) -> Unit)?,
private val pathResolver: (File) -> String) {
private val nameTableBuilder = NameTable.newBuilder()
private val stringTableBuilder = StringTable.newBuilder()
private val nameMap = mutableMapOf<JsName, Int>()
@@ -123,6 +124,8 @@ class JsAstSerializer(private val pathResolver: (File) -> String) {
fragmentBuilder.addInlinedFunctionWrappers(builder.build())
}
jsAstValidator?.let { it(fragment, nameMap.keys) }
return fragmentBuilder.build()
}
@@ -55,7 +55,7 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
orderedModules.asReversed().forEach { module ->
val baseOutputPath = module.outputFileName(file)
val translator = K2JSTranslator(createConfig(module, file, modules))
val translator = K2JSTranslator(createConfig(module, file, modules), true)
val units = module.files.map { TranslationUnit.SourceFile(createPsiFile(it.fileName)) }
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, MainCallParameters.noCall())
@@ -461,7 +461,7 @@ abstract class BasicBoxTest(
runtime: JsIrTestRuntime,
isMainModule: Boolean
) {
val translator = K2JSTranslator(config)
val translator = K2JSTranslator(config, true)
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters)
if (translationResult !is TranslationResult.Success) {
@@ -24,10 +24,8 @@ import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.coroutine.CoroutineTransformer
import org.jetbrains.kotlin.js.facade.exceptions.TranslationException
import org.jetbrains.kotlin.js.inline.JsInliner
import org.jetbrains.kotlin.js.inline.clean.LabeledBlockToDoWhileTransformation
import org.jetbrains.kotlin.js.inline.clean.*
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.translate.general.Translation
@@ -43,7 +41,12 @@ import java.io.IOException
import java.util.ArrayList
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError
import org.jetbrains.kotlin.js.backend.ast.JsBlock
import org.jetbrains.kotlin.js.backend.ast.JsName
import org.jetbrains.kotlin.js.backend.ast.JsProgramFragment
import org.jetbrains.kotlin.js.backend.ast.JsStatement
import org.jetbrains.kotlin.js.coroutine.transformCoroutines
import org.jetbrains.kotlin.js.inline.util.collectDefinedNamesInAllScopes
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf
@@ -51,7 +54,10 @@ import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf
/**
* An entry point of translator.
*/
class K2JSTranslator(private val config: JsConfig) {
class K2JSTranslator @JvmOverloads constructor(
private val config: JsConfig,
private val shouldValidateJsAst: Boolean = false
) {
private val incrementalResults = config.configuration.get(JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER)
@@ -165,7 +171,7 @@ class K2JSTranslator(private val config: JsConfig) {
) {
if (incrementalResults == null) return
val serializer = JsAstSerializer { file ->
val serializer = JsAstSerializer(if (shouldValidateJsAst) ::validateJsAst else null) { file ->
try {
pathResolver.getPathRelativeToSourceRoots(file)
} catch (e: IOException) {
@@ -205,4 +211,26 @@ class K2JSTranslator(private val config: JsConfig) {
headerBuilder.build().writeTo(output)
return output.toByteArray()
}
private fun validateJsAst(fragment: JsProgramFragment, serializedNames: Set<JsName>) {
val knownNames = mutableSetOf<JsName>().apply {
fragment.nameBindings.mapTo(this) { it.name }
fragment.importedModules.mapTo(this) { it.internalName }
}
val allCode = JsBlock(mutableListOf<JsStatement>().apply {
add(fragment.declarationBlock)
add(fragment.exportBlock)
add(fragment.initializerBlock)
fragment.tests?.let { add(it) }
fragment.mainFunction?.let { add(it) }
addAll(fragment.inlinedFunctionWrappers.values)
})
val definedNames = collectDefinedNamesInAllScopes(allCode)
serializedNames.forEach {
assert(!it.isTemporary || it in definedNames || it in knownNames) { "JsName ${it.ident} is unbound" }
}
}
}