From 0b45139b34ecc099b7fe7e7631226abe9a44de97 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Mon, 25 Jan 2021 11:52:42 +0300 Subject: [PATCH] [Test] Fix OutOfMemoryError during extracting meta info from test Previously CodeMetaInfoParser saved MatchResults for each meta info in text (and for each meta info it was different string with text because of extracting parsed part from original text), so it led to O(n^2) memory complexity (where n is a size of original text). Such complexity caused OOME in case of big testdata (~1k meta infos for text of 4k lines) --- .../kotlin/codeMetaInfo/CodeMetaInfoParser.kt | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codeMetaInfo/CodeMetaInfoParser.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codeMetaInfo/CodeMetaInfoParser.kt index d56f7e627c0..807d2c4a737 100644 --- a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codeMetaInfo/CodeMetaInfoParser.kt +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codeMetaInfo/CodeMetaInfoParser.kt @@ -21,13 +21,33 @@ object CodeMetaInfoParser { */ private val tagRegex = """([\S&&[^,(){}]]+)([{](.*?)[}])?(\("(.*?)"\))?(, )?""".toRegex() + private class Opening(val index: Int, val tags: String, val startOffset: Int) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Opening + + if (index != other.index) return false + + return true + } + + override fun hashCode(): Int { + return index + } + } + fun getCodeMetaInfoFromText(renderedText: String): List { var text = renderedText - val openingMatchResults = ArrayDeque() - val stackOfOpeningMatchResults = ArrayDeque() - val closingMatchResults = mutableMapOf() + + val openings = ArrayDeque() + val stackOfOpenings = ArrayDeque() + val closingOffsets = mutableMapOf() val result = mutableListOf() + var counter = 0 + while (true) { var openingStartOffset = Int.MAX_VALUE var closingStartOffset = Int.MAX_VALUE @@ -42,22 +62,23 @@ object CodeMetaInfoParser { text = if (openingStartOffset < closingStartOffset) { requireNotNull(opening) - openingMatchResults.addLast(opening) - stackOfOpeningMatchResults.addLast(opening) + val openingMatch = Opening(counter++, opening.groups[2]!!.value, opening.range.first) + openings.addLast(openingMatch) + stackOfOpenings.addLast(openingMatch) text.removeRange(openingStartOffset, opening.range.last + 1) } else { requireNotNull(closing) - closingMatchResults[stackOfOpeningMatchResults.removeLast()] = closing + closingOffsets[stackOfOpenings.removeLast()] = closing.range.first text.removeRange(closingStartOffset, closing.range.last + 1) } } - if (openingMatchResults.size != closingMatchResults.size) { + if (openings.size != closingOffsets.size) { error("Opening and closing tags counts are not equals") } - while (!openingMatchResults.isEmpty()) { - val openingMatchResult = openingMatchResults.removeLast() - val closingMatchResult = closingMatchResults.getValue(openingMatchResult) - val allMetaInfos = openingMatchResult.groups[2]!!.value + while (!openings.isEmpty()) { + val openingMatchResult = openings.removeLast() + val closingMatchResult = closingOffsets.getValue(openingMatchResult) + val allMetaInfos = openingMatchResult.tags tagRegex.findAll(allMetaInfos).map { it.groups }.forEach { val tag = it[1]!!.value val attributes = it[3]?.value?.split(";") ?: emptyList() @@ -65,8 +86,8 @@ object CodeMetaInfoParser { result.add( ParsedCodeMetaInfo( - openingMatchResult.range.first, - closingMatchResult.range.first, + openingMatchResult.startOffset, + closingMatchResult, attributes.toMutableList(), tag, description