JS: validate AST when serializing to IC cache
This commit is contained in:
+1
-1
@@ -40,7 +40,7 @@ abstract class AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation : Abstra
|
|||||||
val diagnostics = analysisResult.bindingTrace.bindingContext.diagnostics
|
val diagnostics = analysisResult.bindingTrace.bindingContext.diagnostics
|
||||||
|
|
||||||
if (!hasError(diagnostics)) {
|
if (!hasError(diagnostics)) {
|
||||||
val translator = K2JSTranslator(config)
|
val translator = K2JSTranslator(config, true)
|
||||||
translator.translate(object : JsConfig.Reporter() {}, files, MainCallParameters.noCall(), analysisResult)
|
translator.translate(object : JsConfig.Reporter() {}, files, MainCallParameters.noCall(), analysisResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ fun collectDefinedNames(scope: JsNode): Set<JsName> {
|
|||||||
super.visitExpressionStatement(x)
|
super.visitExpressionStatement(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitBreak(x: JsBreak) {
|
||||||
|
x.label?.name?.let { names += it }
|
||||||
|
super.visitBreak(x)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitCatch(x: JsCatch) {
|
override fun visitCatch(x: JsCatch) {
|
||||||
names += x.parameter.name
|
names += x.parameter.name
|
||||||
super.visitCatch(x)
|
super.visitCatch(x)
|
||||||
@@ -134,6 +139,16 @@ fun collectDefinedNamesInAllScopes(scope: JsNode): Set<JsName> {
|
|||||||
x.name?.let { names += it }
|
x.name?.let { names += it }
|
||||||
names += x.parameters.map { it.name }
|
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)
|
}.accept(scope)
|
||||||
|
|
||||||
return names
|
return names
|
||||||
@@ -238,26 +253,6 @@ fun extractFunction(expression: JsExpression) = when (expression) {
|
|||||||
else -> InlineMetadata.decompose(expression)?.function ?: InlineMetadata.tryExtractFunction(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> {
|
fun <T : JsNode> collectInstances(klass: Class<T>, scope: JsNode): List<T> {
|
||||||
return with(InstanceCollector(klass, visitNestedDeclarations = false)) {
|
return with(InstanceCollector(klass, visitNestedDeclarations = false)) {
|
||||||
accept(scope)
|
accept(scope)
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ import java.io.OutputStream
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineStrategy as KotlinInlineStrategy
|
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 nameTableBuilder = NameTable.newBuilder()
|
||||||
private val stringTableBuilder = StringTable.newBuilder()
|
private val stringTableBuilder = StringTable.newBuilder()
|
||||||
private val nameMap = mutableMapOf<JsName, Int>()
|
private val nameMap = mutableMapOf<JsName, Int>()
|
||||||
@@ -123,6 +124,8 @@ class JsAstSerializer(private val pathResolver: (File) -> String) {
|
|||||||
fragmentBuilder.addInlinedFunctionWrappers(builder.build())
|
fragmentBuilder.addInlinedFunctionWrappers(builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsAstValidator?.let { it(fragment, nameMap.keys) }
|
||||||
|
|
||||||
return fragmentBuilder.build()
|
return fragmentBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
|||||||
orderedModules.asReversed().forEach { module ->
|
orderedModules.asReversed().forEach { module ->
|
||||||
val baseOutputPath = module.outputFileName(file)
|
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 units = module.files.map { TranslationUnit.SourceFile(createPsiFile(it.fileName)) }
|
||||||
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, MainCallParameters.noCall())
|
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, MainCallParameters.noCall())
|
||||||
|
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ abstract class BasicBoxTest(
|
|||||||
runtime: JsIrTestRuntime,
|
runtime: JsIrTestRuntime,
|
||||||
isMainModule: Boolean
|
isMainModule: Boolean
|
||||||
) {
|
) {
|
||||||
val translator = K2JSTranslator(config)
|
val translator = K2JSTranslator(config, true)
|
||||||
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters)
|
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters)
|
||||||
|
|
||||||
if (translationResult !is TranslationResult.Success) {
|
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.analyzer.JsAnalysisResult
|
||||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||||
import org.jetbrains.kotlin.js.config.JsConfig
|
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.facade.exceptions.TranslationException
|
||||||
import org.jetbrains.kotlin.js.inline.JsInliner
|
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.inline.clean.*
|
||||||
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
|
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
|
||||||
import org.jetbrains.kotlin.js.translate.general.Translation
|
import org.jetbrains.kotlin.js.translate.general.Translation
|
||||||
@@ -43,7 +41,12 @@ import java.io.IOException
|
|||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError
|
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.coroutine.transformCoroutines
|
||||||
|
import org.jetbrains.kotlin.js.inline.util.collectDefinedNamesInAllScopes
|
||||||
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||||
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf
|
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.
|
* 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)
|
private val incrementalResults = config.configuration.get(JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER)
|
||||||
|
|
||||||
@@ -165,7 +171,7 @@ class K2JSTranslator(private val config: JsConfig) {
|
|||||||
) {
|
) {
|
||||||
if (incrementalResults == null) return
|
if (incrementalResults == null) return
|
||||||
|
|
||||||
val serializer = JsAstSerializer { file ->
|
val serializer = JsAstSerializer(if (shouldValidateJsAst) ::validateJsAst else null) { file ->
|
||||||
try {
|
try {
|
||||||
pathResolver.getPathRelativeToSourceRoots(file)
|
pathResolver.getPathRelativeToSourceRoots(file)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
@@ -205,4 +211,26 @@ class K2JSTranslator(private val config: JsConfig) {
|
|||||||
headerBuilder.build().writeTo(output)
|
headerBuilder.build().writeTo(output)
|
||||||
return output.toByteArray()
|
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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user