Skip to content

Commit 9b7df1a

Browse files
authored
Merge pull request #3502 from EvilBeaver/feature/cfg
Убирает заплатку, сделанную для #1774
2 parents 92afd65 + 68f36c2 commit 9b7df1a

File tree

11 files changed

+334
-54
lines changed

11 files changed

+334
-54
lines changed

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/BasicBlockVertex.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,12 @@ public Optional<BSLParserRuleContext> getAst() {
4747

4848
return Optional.of(statements.get(0));
4949
}
50+
51+
@Override
52+
public String toString() {
53+
if (statements.isEmpty())
54+
return "<empty block>";
55+
56+
return super.toString();
57+
}
5058
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*/
2222
package com.github._1c_syntax.bsl.languageserver.cfg;
2323

24+
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
2425
import com.github._1c_syntax.bsl.parser.BSLParser;
2526
import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor;
2627
import com.github._1c_syntax.bsl.parser.BSLParserRuleContext;
@@ -40,6 +41,8 @@ public class CfgBuildingParseTreeVisitor extends BSLParserBaseVisitor<ParseTree>
4041
private boolean producePreprocessorConditionsEnabled = true;
4142
private boolean adjacentDeadCodeEnabled = false;
4243

44+
private boolean hasTopLevelPreprocessor = false;
45+
4346
public void produceLoopIterations(boolean enable) {
4447
produceLoopIterationsEnabled = enable;
4548
}
@@ -64,6 +67,21 @@ public ControlFlowGraph buildGraph(BSLParser.CodeBlockContext block) {
6467

6568
blocks.enterBlock(exitPoints);
6669

70+
if (producePreprocessorConditionsEnabled) {
71+
// Если это тело модуля, то самую первую инструкцию препроцессора сожрет грамматика file
72+
// надо ее тоже посетить принудительно.
73+
var parent = block.getParent();
74+
if (parent instanceof BSLParser.FileCodeBlockContext fileBlock) {
75+
var probablyPreprocessor = Trees.getPreviousNode(fileBlock.getParent(), fileBlock,
76+
BSLParser.RULE_preprocessor);
77+
78+
if (probablyPreprocessor != fileBlock) {
79+
hasTopLevelPreprocessor = true;
80+
probablyPreprocessor.accept(this);
81+
}
82+
}
83+
}
84+
6785
block.accept(this);
6886

6987
var builtBlock = blocks.leaveBlock();
@@ -109,8 +127,7 @@ public ParseTree visitIfStatement(BSLParser.IfStatementContext ctx) {
109127
connectGraphTail(blocks.getCurrentBlock(), conditionStatement);
110128

111129
// подграф if
112-
blocks.enterBlock();
113-
var currentLevelBlock = blocks.getCurrentBlock();
130+
var currentLevelBlock = blocks.enterBlock();
114131

115132
// тело true
116133
blocks.enterBlock();
@@ -154,6 +171,7 @@ public ParseTree visitIfStatement(BSLParser.IfStatementContext ctx) {
154171
if (hasNoSignificantEdges(blockTail)
155172
&& blockTail instanceof BasicBlockVertex basicBlock
156173
&& basicBlock.statements().isEmpty()) {
174+
graph.removeVertex(basicBlock);
157175
continue;
158176
}
159177
graph.addEdge(blockTail, upperBlock.end());
@@ -375,23 +393,29 @@ public ParseTree visitPreproc_if(BSLParser.Preproc_ifContext ctx) {
375393
return ctx;
376394
}
377395

378-
if (!isStatementLevelPreproc(ctx)) {
396+
if (hasTopLevelPreprocessor) {
397+
398+
var currentBlock = blocks.getCurrentBlock();
399+
graph.addVertex(currentBlock.begin());
400+
401+
hasTopLevelPreprocessor = false;
402+
403+
} else if (!isStatementLevelPreproc(ctx)) {
379404
return super.visitPreproc_if(ctx);
380405
}
381406

382-
var node = new PreprocessorConditionVertex(ctx);
383-
graph.addVertex(node);
384-
connectGraphTail(blocks.getCurrentBlock(), node);
407+
var conditionVertex = new PreprocessorConditionVertex(ctx);
408+
graph.addVertex(conditionVertex);
409+
connectGraphTail(blocks.getCurrentBlock(), conditionVertex);
385410

386-
var mainIf = blocks.enterBlock();
387-
var body = blocks.enterBlock(); // тело идущего следом блока
388-
389-
graph.addVertex(body.begin());
390-
graph.addEdge(node, body.begin(), CfgEdgeType.TRUE_BRANCH);
411+
blocks.enterBlock();
412+
var truePart = blocks.enterBlock(); // тело идущего следом блока
391413

392-
body.getBuildParts().push(node);
414+
graph.addVertex(truePart.begin());
415+
graph.addEdge(conditionVertex, truePart.begin(), CfgEdgeType.TRUE_BRANCH);
393416

394-
mainIf.getBuildParts().push(body.begin());
417+
// маркерный узел для опознания в elseif/endif
418+
truePart.getBuildParts().push(conditionVertex);
395419

396420
return super.visitPreproc_if(ctx);
397421
}
@@ -446,6 +470,7 @@ public ParseTree visitPreproc_elsif(BSLParser.Preproc_elsifContext ctx) {
446470
graph.addVertex(body.begin());
447471
graph.addEdge(newCondition, body.begin(), CfgEdgeType.TRUE_BRANCH);
448472

473+
// маркерный узел для опознания в elseif/endif
449474
body.getBuildParts().push(newCondition);
450475

451476
return ctx;
@@ -466,21 +491,26 @@ public ParseTree visitPreproc_endif(BSLParser.Preproc_endifContext ctx) {
466491
}
467492

468493
var previousBody = blocks.leaveBlock();
469-
var mainIf = blocks.leaveBlock();
470-
mainIf.getBuildParts().push(previousBody.end());
494+
var conditionSubgraph = blocks.leaveBlock();
495+
496+
// Если в блоке if была ветка else/elsif, то из первого if уже существует ветка FALSE_BRANCH.
497+
// А если альтернатив у if не было, то надо добавить FALSE_BRANCH
498+
// Методы preproc_elsif/preproc_else добавят свои следы в conditionSubgraph
499+
// А если там пусто, то у нас есть только ветка true
500+
boolean mustAddFalseBranch = conditionSubgraph.getBuildParts().isEmpty();
501+
502+
conditionSubgraph.getBuildParts().push(previousBody.end());
471503

472504
var upperBlock = blocks.getCurrentBlock();
473505
upperBlock.split();
474506
graph.addVertex(upperBlock.end());
475507

476-
// если блоки альтернатив были, то у условия будет уже 2 выхода
477-
if (graph.outgoingEdgesOf(condition).size() < 2) {
508+
if (mustAddFalseBranch)
478509
graph.addEdge(condition, upperBlock.end(), CfgEdgeType.FALSE_BRANCH);
479-
}
480510

481511
// присоединяем все прямые выходы из тел условий
482-
while (!mainIf.getBuildParts().isEmpty()) {
483-
var blockTail = mainIf.getBuildParts().pop();
512+
while (!conditionSubgraph.getBuildParts().isEmpty()) {
513+
var blockTail = conditionSubgraph.getBuildParts().pop();
484514

485515
// это мертвый код. Он может быть пустым блоком
486516
// тогда он не нужен сам по себе
@@ -557,14 +587,15 @@ private void connectGraphTail(StatementsBlockWriter.StatementsBlockRecord curren
557587

558588
if (currentTail.statements().isEmpty()) {
559589
// перевести все связи на новую вершину
560-
var incoming = graph.incomingEdgesOf(currentTail);
590+
var incoming = graph.incomingEdgesOf(currentTail).stream().toList();
561591
for (var edge : incoming) {
562592
// ребра смежности не переключаем, т.к. текущий блок удаляется
563593
if (edge.getType() == CfgEdgeType.ADJACENT_CODE) {
564594
continue;
565595
}
566596

567597
var source = graph.getEdgeSource(edge);
598+
graph.removeEdge(edge);
568599
graph.addEdge(source, vertex, edge.getType());
569600
}
570601
graph.removeVertex(currentTail);

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgVertex.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,38 @@
2626
import java.util.Optional;
2727

2828
public abstract class CfgVertex {
29+
30+
private boolean isConnected;
31+
2932
public Optional<BSLParserRuleContext> getAst() {
3033
return Optional.empty();
3134
}
35+
36+
@Override
37+
public String toString() {
38+
return getAst().map(
39+
ast -> getClass().getSimpleName() +
40+
"{" + ast.getStart().getLine() + ":" + ast.getStop().getLine() + "}")
41+
.orElseGet(() -> getClass().getSimpleName());
42+
}
43+
44+
protected void onConnectOutgoing(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
45+
if (!isConnected) {
46+
// Шорткат, чтобы не ходить для новых нод в граф
47+
isConnected = true;
48+
return;
49+
}
50+
51+
graph.outgoingEdgesOf(this).stream()
52+
.filter(existing -> existing.getType() == edge.getType())
53+
.findAny()
54+
.ifPresent((CfgEdge existing) -> {
55+
throw duplicateLinkError(graph, target, existing);
56+
});
57+
}
58+
59+
private FlowGraphLinkException duplicateLinkError(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
60+
throw new FlowGraphLinkException("Can't add edge " + this + "->" + target + "\n"
61+
+ "Source vertex " + this + " already has " + edge.getType() + " edge " + graph.edgePresentation(edge));
62+
}
3263
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ConditionalVertex.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,14 @@ public BSLParser.ExpressionContext getExpression() {
5353
public Optional<BSLParserRuleContext> getAst() {
5454
return Optional.of(ast);
5555
}
56+
57+
@Override
58+
protected void onConnectOutgoing(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
59+
super.onConnectOutgoing(graph, target, edge);
60+
61+
if (edge.getType() != CfgEdgeType.TRUE_BRANCH && edge.getType() != CfgEdgeType.FALSE_BRANCH) {
62+
throw new FlowGraphLinkException("Can't add edge " + this + "-> " + target + "\n"
63+
+"Edge type " + edge.getType() + " is forbidden here.");
64+
}
65+
}
5666
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraph.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class ControlFlowGraph extends DefaultDirectedGraph<CfgVertex, CfgEdge> {
3131
@Setter
3232
private CfgVertex entryPoint;
3333

34-
private ExitVertex exitPoint;
34+
private final ExitVertex exitPoint;
3535

3636
ControlFlowGraph() {
3737
super(CfgEdge.class);
@@ -43,4 +43,24 @@ public void addEdge(CfgVertex source, CfgVertex target, CfgEdgeType type) {
4343
addEdge(source, target, new CfgEdge(type));
4444
}
4545

46+
@Override
47+
public CfgEdge addEdge(CfgVertex source, CfgVertex target) {
48+
var edge = new CfgEdge(CfgEdgeType.DIRECT);
49+
50+
addEdge(source, target, edge);
51+
52+
return edge;
53+
}
54+
55+
@Override
56+
public boolean addEdge(CfgVertex source, CfgVertex target, CfgEdge edge) {
57+
source.onConnectOutgoing(this, target, edge);
58+
59+
return super.addEdge(source, target, edge);
60+
}
61+
62+
public String edgePresentation(CfgEdge edge) {
63+
return getEdgeSource(edge) + "->" + getEdgeTarget(edge);
64+
}
65+
4666
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ public CfgEdge walkNext() {
4545
var edges = availableRoutes();
4646
var edgeOrNot = edges.stream()
4747
.filter(x -> x.getType() == CfgEdgeType.DIRECT)
48-
.findFirst();
48+
.reduce((CfgEdge a, CfgEdge b) -> {
49+
throw new IllegalStateException("Multiple DIRECT outgoing edges in " + currentNode);
50+
});
4951

5052
if (edgeOrNot.isPresent()) {
5153
currentNode = graph.getEdgeTarget(edgeOrNot.get());
@@ -60,7 +62,7 @@ public CfgEdge walkNext(CfgEdgeType edgeType) {
6062
.filter(x -> x.getType() == edgeType)
6163
.findAny();
6264

63-
if(edgeOrNot.isPresent()) {
65+
if (edgeOrNot.isPresent()) {
6466
currentNode = graph.getEdgeTarget(edgeOrNot.get());
6567
return edgeOrNot.get();
6668
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ExitVertex.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@
2222
package com.github._1c_syntax.bsl.languageserver.cfg;
2323

2424
public class ExitVertex extends CfgVertex {
25+
@Override
26+
protected void onConnectOutgoing(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
27+
throw new FlowGraphLinkException("ExitNode can't have outgoing edges");
28+
}
2529
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2025
5+
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
6+
*
7+
* SPDX-License-Identifier: LGPL-3.0-or-later
8+
*
9+
* BSL Language Server is free software; you can redistribute it and/or
10+
* modify it under the terms of the GNU Lesser General Public
11+
* License as published by the Free Software Foundation; either
12+
* version 3.0 of the License, or (at your option) any later version.
13+
*
14+
* BSL Language Server is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
* Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public
20+
* License along with BSL Language Server.
21+
*/
22+
package com.github._1c_syntax.bsl.languageserver.cfg;
23+
24+
public class FlowGraphLinkException extends RuntimeException {
25+
public FlowGraphLinkException(String message) {
26+
super(message);
27+
}
28+
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AllFunctionPathMustHaveReturnDiagnostic.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import com.github._1c_syntax.bsl.languageserver.cfg.CfgVertex;
2828
import com.github._1c_syntax.bsl.languageserver.cfg.ConditionalVertex;
2929
import com.github._1c_syntax.bsl.languageserver.cfg.ControlFlowGraph;
30-
import com.github._1c_syntax.bsl.languageserver.cfg.ExitVertex;
3130
import com.github._1c_syntax.bsl.languageserver.cfg.LoopVertex;
3231
import com.github._1c_syntax.bsl.languageserver.cfg.WhileLoopVertex;
3332
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
@@ -98,18 +97,12 @@ public ParseTree visitFunction(BSLParser.FunctionContext ctx) {
9897

9998
private void checkAllPathsHaveReturns(BSLParser.FunctionContext ctx) {
10099
var builder = new CfgBuildingParseTreeVisitor();
101-
builder.producePreprocessorConditions(false);
100+
builder.producePreprocessorConditions(true);
102101
var graph = builder.buildGraph(ctx.subCodeBlock().codeBlock());
103102

104-
var exitNode = graph.vertexSet().stream()
105-
.filter(ExitVertex.class::isInstance)
106-
.findFirst();
103+
var exitNode = graph.getExitPoint();
107104

108-
if (exitNode.isEmpty()) {
109-
throw new IllegalStateException();
110-
}
111-
112-
var incomingVertices = graph.incomingEdgesOf(exitNode.get()).stream()
105+
var incomingVertices = graph.incomingEdgesOf(exitNode).stream()
113106
.map(graph::getEdgeSource)
114107
.map(vertex -> nonExplicitReturnNode(vertex, graph))
115108
.flatMap(Optional::stream)

0 commit comments

Comments
 (0)