diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java index e4f4e2110c7..e27581ab510 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java @@ -131,11 +131,12 @@ public ParseTree visitIfStatement(BSLParser.IfStatementContext ctx) { // тело true blocks.enterBlock(); - if (ctx.ifBranch().codeBlock() != null) { + if (ctx.ifBranch().codeBlock() != null && !Trees.nodeContainsErrors(ctx.ifBranch())) { ctx.ifBranch().codeBlock().accept(this); } var truePart = blocks.leaveBlock(); + graph.addVertex(truePart.begin()); graph.addEdge(conditionStatement, truePart.begin(), CfgEdgeType.TRUE_BRANCH); currentLevelBlock.getBuildParts().push(truePart.end()); currentLevelBlock.getBuildParts().push(conditionStatement); @@ -198,9 +199,12 @@ public ParseTree visitElsifBranch(BSLParser.ElsifBranchContext ctx) { // тело true blocks.enterBlock(); - ctx.codeBlock().accept(this); + if (ctx.codeBlock() != null && !Trees.nodeContainsErrors(ctx)) { + ctx.codeBlock().accept(this); + } var truePart = blocks.leaveBlock(); + graph.addVertex(truePart.begin()); graph.addEdge(condition, truePart.begin(), CfgEdgeType.TRUE_BRANCH); blocks.getCurrentBlock().getBuildParts().push(truePart.end()); blocks.getCurrentBlock().getBuildParts().push(condition); @@ -218,11 +222,14 @@ public ParseTree visitCodeBlock(BSLParser.CodeBlockContext ctx) { @Override public ParseTree visitElseBranch(BSLParser.ElseBranchContext ctx) { blocks.enterBlock(); - ctx.codeBlock().accept(this); + if (ctx.codeBlock() != null && !Trees.nodeContainsErrors(ctx)) { + ctx.codeBlock().accept(this); + } var block = blocks.leaveBlock(); // на стеке находится условие var condition = blocks.getCurrentBlock().getBuildParts().pop(); + graph.addVertex(block.begin()); graph.addEdge(condition, block.begin(), CfgEdgeType.FALSE_BRANCH); blocks.getCurrentBlock().getBuildParts().push(block.end()); diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgEmptyStringDotTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgEmptyStringDotTest.java new file mode 100644 index 00000000000..4d9642de37b --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgEmptyStringDotTest.java @@ -0,0 +1,132 @@ +package com.github._1c_syntax.bsl.languageserver.cfg; + +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +class CfgEmptyStringDotTest { + + @Test + void testEmptyStringWithDotShouldNotCrash() { + var code = """ + Процедура РаспределитьНаТовары(Команда) + + Если Товар.ТаможеннаяСтоимость = "". Тогда + + КонецЕсли; + + КонецПроцедуры + """; + + var dContext = TestUtils.getDocumentContext(code); + var parseTree = dContext.getAst().subs().sub(0).procedure().subCodeBlock().codeBlock(); + + var builder = new CfgBuildingParseTreeVisitor(); + builder.producePreprocessorConditions(true); + builder.produceLoopIterations(false); + builder.determineAdjacentDeadCode(false); + + // This should not crash + var graph = builder.buildGraph(parseTree); + + // Basic validation that the graph was created + assertThat(graph).isNotNull(); + assertThat(graph.getEntryPoint()).isNotNull(); + assertThat(graph.getExitPoint()).isNotNull(); + } + + @Test + void testEmptyElsifBranchShouldNotCrash() { + var code = """ + Процедура Тест() + + Если Условие1 Тогда + Действие1(); + ИначеЕсли Условие2 = "". Тогда + + КонецЕсли; + + КонецПроцедуры + """; + + var dContext = TestUtils.getDocumentContext(code); + var parseTree = dContext.getAst().subs().sub(0).procedure().subCodeBlock().codeBlock(); + + var builder = new CfgBuildingParseTreeVisitor(); + builder.producePreprocessorConditions(true); + builder.produceLoopIterations(false); + builder.determineAdjacentDeadCode(false); + + // This should not crash + var graph = builder.buildGraph(parseTree); + + // Basic validation that the graph was created + assertThat(graph).isNotNull(); + assertThat(graph.getEntryPoint()).isNotNull(); + assertThat(graph.getExitPoint()).isNotNull(); + } + + @Test + void testEmptyElseBranchShouldNotCrash() { + var code = """ + Процедура Тест() + + Если Условие1 Тогда + Действие1(); + Иначе + + КонецЕсли; + + КонецПроцедуры + """; + + var dContext = TestUtils.getDocumentContext(code); + var parseTree = dContext.getAst().subs().sub(0).procedure().subCodeBlock().codeBlock(); + + var builder = new CfgBuildingParseTreeVisitor(); + builder.producePreprocessorConditions(true); + builder.produceLoopIterations(false); + builder.determineAdjacentDeadCode(false); + + // This should not crash + var graph = builder.buildGraph(parseTree); + + // Basic validation that the graph was created + assertThat(graph).isNotNull(); + assertThat(graph.getEntryPoint()).isNotNull(); + assertThat(graph.getExitPoint()).isNotNull(); + } + + @Test + void testOriginalIssueCase() { + // This is the exact code from the issue report + var code = """ + Процедура Тест() + + Если Товар.ТаможеннаяСтоимость = "". Тогда + + КонецЕсли; + + КонецПроцедуры + """; + + var dContext = TestUtils.getDocumentContext(code); + var parseTree = dContext.getAst().subs().sub(0).procedure().subCodeBlock().codeBlock(); + + var builder = new CfgBuildingParseTreeVisitor(); + builder.producePreprocessorConditions(true); + builder.produceLoopIterations(false); + builder.determineAdjacentDeadCode(false); + + // This should not crash (was crashing before the fix) + var graph = builder.buildGraph(parseTree); + + // Basic validation that the graph was created + assertThat(graph).isNotNull(); + assertThat(graph.getEntryPoint()).isNotNull(); + assertThat(graph.getExitPoint()).isNotNull(); + } +}