Skip to content

Убирает заплатку, сделанную для #1774#3502

Merged
nixel2007 merged 14 commits into
1c-syntax:developfrom
EvilBeaver:feature/cfg
Aug 20, 2025
Merged

Убирает заплатку, сделанную для #1774#3502
nixel2007 merged 14 commits into
1c-syntax:developfrom
EvilBeaver:feature/cfg

Conversation

@EvilBeaver

Copy link
Copy Markdown
Collaborator

Описание

Чеклист

Общие

  • Ветка PR обновлена из develop
  • Отладочные, закомментированные и прочие, не имеющие смысла участки кода удалены
  • Изменения покрыты тестами
  • Обязательные действия перед коммитом выполнены (запускал команду gradlew precommit)

@coderabbitai

coderabbitai Bot commented Aug 19, 2025

Copy link
Copy Markdown
Contributor

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Добавлены проверки и защита при соединении вершин CFG (включая запрет исходящих рёбер у ExitVertex), введено исключение FlowGraphLinkException, расширен API ControlFlowGraph (новые addEdge/edgePresentation), улучшена обработка верхнеуровневых препроцессоров в построителе CFG, скорректирован обход графа и обновлены тесты/диагностика.

Changes

Cohort / File(s) Summary
CFG: ядро вершин и исключения
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgVertex.java, .../ConditionalVertex.java, .../ExitVertex.java, .../FlowGraphLinkException.java
Добавлен хук onConnectOutgoing с защитой от дублирующих связей и короткой меткой connected; ConditionalVertex уточняет допустимые типы исходящих рёбер; ExitVertex запрещает исходящие рёбра (бросает FlowGraphLinkException); добавлен класс FlowGraphLinkException; toString у вершин.
ControlFlowGraph API
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraph.java
exitPoint сделан final и инициализируется в конструкторе; добавлены перегрузки addEdge(source,target) и addEdge(source,target,edge) с вызовом onConnectOutgoing; добавлен helper edgePresentation(CfgEdge).
BasicBlock представление
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/BasicBlockVertex.java
Добавлен override toString(): возвращает "" для пустых блоков, иначе делегирует суперклассу.
Построитель CFG и препроцессор
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java
Введён флаг hasTopLevelPreprocessor; при включённых препроцессорных условиях обнаружение и предварительная обработка верхнеуровневого preproc; переработано ветвление preproc_if/elsif/else/endif (truePart-блок, условная подстановка FALSE_BRANCH, безопасное переназначение рёбер пустых блоков).
Обход графа
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java
Жёсткая проверка единственного подходящего исходящего ребра: замена findFirst/findAny на reduce с явным исключением при 0 или >1 подходящих рёбер.
Диагностика возвратов
src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AllFunctionPathMustHaveReturnDiagnostic.java
producePreprocessorConditions(true); использование graph.getExitPoint() вместо поиска ExitVertex; упрощение обработки входящих рёбер.
Тесты: CFG и диагностики
src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java, .../AllFunctionPathMustHaveReturnDiagnosticTest.java
Обновлены/добавлены тесты для топ-level препроцессора, ограничений на добавление рёбер и запрещённых исходящих рёбер; модернизированы ассерты и мок-структуры (CommonToken, mock/when).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Builder as CFG Builder
  participant CFG as ControlFlowGraph
  participant Src as CfgVertex (source)
  participant Super as GraphSuper
  participant Ex as FlowGraphLinkException

  Builder->>CFG: addEdge(source,target,edge)
  CFG->>Src: onConnectOutgoing(graph,target,edge)
  alt Первое соединение узла
    Src-->>CFG: пометка connected, return
  else Повторное соединение
    Src->>Src: проверка существующих outgoing ребер
    alt Найден дубликат типа
      Src-->>CFG: throw FlowGraphLinkException
      CFG-->>Builder: exception
    else Нет дубликата
      Src-->>CFG: return
    end
  end
  CFG->>Super: super.addEdge(source,target,edge)
  Super-->>CFG: результат
  CFG-->>Builder: результат
Loading
sequenceDiagram
  autonumber
  participant Visitor as CfgBuildingParseTreeVisitor
  participant Trees as Trees
  participant Blocks as BlockStack
  participant CFG as ControlFlowGraph

  Visitor->>Trees: найти предыдущий узел (top-level preproc?)
  alt Top-level preproc найден
    Visitor->>Visitor: hasTopLevelPreprocessor = true
    Visitor->>Visitor: visit preproc_if первым
    Visitor->>Blocks: enterBlock() для truePart
    Visitor->>CFG: addEdge(condition -> truePart.begin, TRUE_BRANCH)
    Visitor->>Visitor: пометить conditionVertex в truePart.buildParts
    Visitor->>Visitor: при endif — склеить хвосты, при необходимости добавить FALSE_BRANCH
  else Не найден
    Visitor->>Visitor: обычная обработка тела модуля
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • theshadowco

Poem

Я кролик, прыг — по рёбрам скок,
Связи проверил, дубль — недосток.
Выход заперт, порядок строг,
Препроцессор встретил у порога — шок.
Тесты поели морковку, всё впрок. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0e367e4 and 68f36c2.

📒 Files selected for processing (1)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ConditionalVertex.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ConditionalVertex.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Analyse
  • GitHub Check: build (17, ubuntu-latest)
  • GitHub Check: build (21, macOS-latest)
  • GitHub Check: build (21, windows-latest)
  • GitHub Check: build (23, macOS-latest)
  • GitHub Check: build (23, windows-latest)
  • GitHub Check: build (17, windows-latest)
  • GitHub Check: build (17, macOS-latest)
  • GitHub Check: build (23, ubuntu-latest)
  • GitHub Check: build (21, ubuntu-latest)
  • GitHub Check: build
  • GitHub Check: Benchmark
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (13)
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/FlowGraphLinkException.java (1)

24-28: Добавьте serialVersionUID и конструкторы с cause для улучшения диагностики

Многие анализаторы (SpotBugs/IDEA) предупреждают об отсутствующем serialVersionUID у Serializable-исключений; дополнительные конструкторы упростят проброс первопричины.

 public class FlowGraphLinkException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
   public FlowGraphLinkException(String message) {
     super(message);
   }
+  public FlowGraphLinkException(String message, Throwable cause) {
+    super(message, cause);
+  }
+  public FlowGraphLinkException(Throwable cause) {
+    super(cause);
+  }
 }
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/BasicBlockVertex.java (1)

51-57: Nit: обрамите if фигурными скобками для единообразия и безопасности будущих правок

Стиль кода в файле преимущественно со скобками; так меньше шансов на ошибку при добавлении строк.

   @Override
   public String toString() {
-    if (statements.isEmpty())
-      return "<empty block>";
+    if (statements.isEmpty()) {
+      return "<empty block>";
+    }
 
     return super.toString();
   }
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ConditionalVertex.java (1)

57-65: Проверяйте тип ребра до вызова super.onConnectOutgoing — fail-fast и отсутствие побочных эффектов

Так мы раньше отфильтруем недопустимые типы и избежим лишней работы/побочных эффектов базовой реализации.

   @Override
   protected void onConnectOutgoing(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
-    super.onConnectOutgoing(graph, target, edge);
-
-    if (edge.getType() != CfgEdgeType.TRUE_BRANCH && edge.getType() != CfgEdgeType.FALSE_BRANCH) {
+    if (edge.getType() != CfgEdgeType.TRUE_BRANCH && edge.getType() != CfgEdgeType.FALSE_BRANCH) {
       throw new FlowGraphLinkException("Can't add edge " + this + "->"+target + "\n"
         +"Edge type " + edge.getType() + " is forbidden here.");
     }
+    super.onConnectOutgoing(graph, target, edge);
   }
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ExitVertex.java (1)

25-28: Сделайте сообщение исключения более информативным (тип ребра и целевая вершина)

Это облегчит диагностику при нарушении инварианта.

   @Override
   protected void onConnectOutgoing(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
-    throw new FlowGraphLinkException("ExitNode can't have outgoing edges");
+    throw new FlowGraphLinkException(
+      "ExitVertex can't have outgoing edges: attempted " + edge.getType() + " to " + target
+    );
   }
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java (1)

48-50: Унифицировать проверку уникальности с walkNext(edgeType)

Сейчас множественность проверяется только для DIRECT; имеет смысл так же контролировать уникальность для произвольного типа в walkNext(CfgEdgeType), либо вынести общую логику в приватный хелпер.

Набросок приватного хелпера и использование в обоих методах:

private Optional<CfgEdge> uniqueEdgeByType(CfgEdgeType edgeType) {
  return availableRoutes().stream()
    .filter(x -> x.getType() == edgeType)
    .reduce((a, b) -> { throw new IllegalStateException("Multiple " + edgeType + " outgoing edges in " + currentNode); });
}

public CfgEdge walkNext() {
  var edgeOrNot = uniqueEdgeByType(CfgEdgeType.DIRECT);
  if (edgeOrNot.isPresent()) {
    currentNode = graph.getEdgeTarget(edgeOrNot.get());
    return edgeOrNot.get();
  }
  throw new IllegalStateException("DIRECT edge is not found for node " + currentNode);
}

public CfgEdge walkNext(CfgEdgeType edgeType) {
  var edgeOrNot = uniqueEdgeByType(edgeType);
  if (edgeOrNot.isPresent()) {
    currentNode = graph.getEdgeTarget(edgeOrNot.get());
    return edgeOrNot.get();
  }
  throw new IllegalStateException("Edge is not found for node " + currentNode);
}
src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AllFunctionPathMustHaveReturnDiagnostic.java (1)

120-125: Избегайте Collector с поставщиком уже существующего списка

Передача поставщика, возвращающего уже созданный список, нарушает контракт Collector и может повести себя неожиданно при изменениях режима выполнения стрима. Проще и безопаснее использовать forEach/addAll.

Предлагаю заменить на прямое добавление:

-    incomingVertices.stream()
-      .map(vertex -> RelatedInformation.create(documentContext.getUri(),
-        Ranges.create(vertex),
-        info.getMessage()))
-      .collect(Collectors.toCollection(() -> listOfMessages));
+    incomingVertices.forEach(vertex ->
+      listOfMessages.add(
+        RelatedInformation.create(documentContext.getUri(), Ranges.create(vertex), info.getMessage())
+      )
+    );
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgVertex.java (2)

30-31: Состояние isConnected — ок как микро-оптимизация, но проверьте жизненный цикл вершин

Флаг позволяет избежать первого обращения к графу, но делает поведение зависящим от жизненного цикла вершины (повторное использование объекта вершины в другом графе теоретически может повлиять). Если такие переиспользования исключены конвенцией — ок; иначе стоит пересмотреть на graph.outgoingEdgesOf(this).isEmpty().

Хотите, подготовлю проверку по коду на предмет переиспользования экземпляров CfgVertex между графами?


59-62: Нит: метод duplicateLinkError бросает исключение сам и при этом используется в выражении throw

Сейчас duplicateLinkError сам делает throw, а снаружи используется как throw duplicateLinkError(...). Это нетипично и сбивает с толку. Лучше пусть метод возвращает исключение, а бросание остается в месте вызова.

-  private FlowGraphLinkException duplicateLinkError(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
-    throw new FlowGraphLinkException("Can't add edge " + this + "->"+target + "\n"
-      +"Source vertex " + this + " already has "+edge.getType()+" edge " + graph.edgePresentation(edge));
-  }
+  private FlowGraphLinkException duplicateLinkError(ControlFlowGraph graph, CfgVertex target, CfgEdge edge) {
+    return new FlowGraphLinkException(
+      "Can't add edge " + this + "->" + target + "\n" +
+      "Source vertex " + this + " already has " + edge.getType() + " edge " + graph.edgePresentation(edge)
+    );
+  }
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraph.java (1)

42-44: Нит: несоответствие возвращаемых типов addEdge-оберток

Сейчас есть void-обертка addEdge(source, target, CfgEdgeType), которая игнорирует boolean-результат базового addEdge. Это консистентно со старым API, но в перспективе можно вернуть boolean для унификации (осторожно: возможен breaking change).

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

70-81: Верхнеуровневый препроцессор: сейчас обрабатывается только ближайший блок; стоит поддержать несколько подряд

Если перед телом модуля идут несколько верхнеуровневых препроцессорных конструкций подряд, текущая логика посетит только ближайшую к FileCodeBlock. Предлагаю пройтись по всем предыдущим узлам RULE_preprocessor и вызвать accept в порядке исходника.

Пример правки:

-    if (producePreprocessorConditionsEnabled) {
-      // Если это тело модуля, то самую первую инструкцию препроцессора сожрет грамматика file
-      // надо ее тоже посетить принудительно.
-      var parent = block.getParent();
-      if (parent instanceof BSLParser.FileCodeBlockContext fileBlock) {
-        var probablyPreprocessor = Trees.getPreviousNode(fileBlock.getParent(), fileBlock , BSLParser.RULE_preprocessor);
-        if (probablyPreprocessor != fileBlock) {
-          hasTopLevelPreprocessor = true;
-          probablyPreprocessor.accept(this);
-        }
-      }
-    }
+    if (producePreprocessorConditionsEnabled) {
+      // Если это тело модуля, грамматика file «съедает» верхнеуровневые препроцессоры.
+      // Пройдем все предыдущие препроцессоры и посетим их в порядке исходника.
+      var parent = block.getParent();
+      if (parent instanceof BSLParser.FileCodeBlockContext fileBlock) {
+        var container = fileBlock.getParent();
+        var node = Trees.getPreviousNode(container, fileBlock, BSLParser.RULE_preprocessor);
+        java.util.Deque<org.antlr.v4.runtime.tree.ParseTree> stack = new java.util.ArrayDeque<>();
+        while (node != fileBlock) {
+          stack.push(node);
+          node = Trees.getPreviousNode(container, node, BSLParser.RULE_preprocessor);
+        }
+        while (!stack.isEmpty()) {
+          hasTopLevelPreprocessor = true;
+          stack.pop().accept(this);
+        }
+      }
+    }

402-405: Защититься от NPE в проверке уровня препроцессора

В вызове isStatementLevelPreproc(ctx) возможен NPE при нетипичном дереве (если у узла не окажется одного из родителей). Редкий случай, но лучше сделать метод null-safe.

Предлагаемая правка метода (вне текущего диапазона строк):

private static boolean isStatementLevelPreproc(BSLParserRuleContext ctx) {
  var parent = ctx.getParent();
  if (parent == null) {
    return false;
  }
  var grandparent = parent.getParent();
  return grandparent != null && grandparent.getRuleIndex() == BSLParser.RULE_statement;
}

492-526: preproc_endif: корректная логика FALSE_BRANCH; возможно избыточный addVertex для хвостов

Логика mustAddFalseBranch устраняет необходимость принудительно добавлять FALSE_BRANCH, когда альтернативы уже заданы ранее — это правильно. Небольшая придирка: вызов graph.addVertex(blockTail) перед добавлением ребра, скорее всего, избыточен — хвост, как правило, уже присутствует в графе. Если ControlFlowGraph не гарантирует идемпотентность addVertex, это может быть источником ошибок; если гарантирует — всё ок.

Мини-правка:

-      graph.addVertex(blockTail);
       graph.addEdge(blockTail, upperBlock.end());

Дополнительно: стоит убедиться, что тесты покрывают оба случая — без else/elsif (добавляется FALSE_BRANCH) и с альтернативами (ветка не добавляется).


589-599: Перекладка входящих ребер: устранен CME; сохранить тип ребра до удаления и проверить базовую JDK

Текущий подход с предварительным копированием списка ребер предотвращает ConcurrentModificationException — это плюс. Для наглядности и на всякий случай лучше сохранить тип ребра в локальную переменную до удаления ребра из графа. Также используется Stream.toList(), требующий JDK 16+; проверьте, соответствует ли это целевой версии проекта.

Предлагаемая правка:

-      var incoming = graph.incomingEdgesOf(currentTail).stream().toList();
+      var incoming = graph.incomingEdgesOf(currentTail).stream().toList();
       for (var edge : incoming) {
         // ребра смежности не переключаем, т.к. текущий блок удаляется
         if (edge.getType() == CfgEdgeType.ADJACENT_CODE) {
           continue;
         }

-        var source = graph.getEdgeSource(edge);
-        graph.removeEdge(edge);
-        graph.addEdge(source, vertex, edge.getType());
+        var source = graph.getEdgeSource(edge);
+        var type = edge.getType();
+        graph.removeEdge(edge);
+        graph.addEdge(source, vertex, type);
       }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd77a6 and 7cd23a7.

📒 Files selected for processing (11)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/BasicBlockVertex.java (1 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java (9 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgVertex.java (1 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ConditionalVertex.java (1 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraph.java (2 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java (1 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ExitVertex.java (1 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/FlowGraphLinkException.java (1 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AllFunctionPathMustHaveReturnDiagnostic.java (1 hunks)
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java (7 hunks)
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AllFunctionPathMustHaveReturnDiagnosticTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-18T22:46:43.245Z
Learnt from: nixel2007
PR: 1c-syntax/bsl-language-server#3449
File: src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionTreeBuildingVisitor.java:192-203
Timestamp: 2025-04-18T22:46:43.245Z
Learning: В проекте bsl-language-server класс BSLParser.ExpressionContext наследуется от BSLParserRuleContext, а не напрямую от ParserRuleContext. При работе с ним нужно учитывать специфичные методы BSLParserRuleContext.

Applied to files:

  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java
🧬 Code Graph Analysis (1)
src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java (1)
src/test/java/com/github/_1c_syntax/bsl/languageserver/util/TestUtils.java (1)
  • TestUtils (37-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (23, ubuntu-latest)
  • GitHub Check: build (21, ubuntu-latest)
  • GitHub Check: build (17, windows-latest)
  • GitHub Check: build (23, windows-latest)
  • GitHub Check: build (21, macOS-latest)
  • GitHub Check: build (21, windows-latest)
  • GitHub Check: build (17, macOS-latest)
  • GitHub Check: build (23, macOS-latest)
  • GitHub Check: build (17, ubuntu-latest)
  • GitHub Check: Analyse
  • GitHub Check: build
  • GitHub Check: Benchmark
🔇 Additional comments (21)
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java (1)

48-50: Хорошее ужесточение: теперь гарантируется единственность DIRECT-ребра

Проверка через reduce с выбросом исключения при >1 ребре делает инвариант очевидным и помогает раннему выявлению ошибок построения графа.

src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AllFunctionPathMustHaveReturnDiagnosticTest.java (1)

105-118: Тест кейс с препроцессором корректно отражает целевую логику выхода из функции

Замена условного блока на директиву препроцессора (#Если/#Иначе) в примере делает тест релевантным новой модели CFG и корректной обработке исключений/возвратов. Ожидание отсутствия диагностик выглядит верным.

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

100-102: Включение условий препроцессора при построении CFG — правильное решение

builder.producePreprocessorConditions(true) синхронизирует диагностику с новой поддержкой препроцессора в CFG. Это предотвращает ложно-положительные срабатывания на путях, отрезанных препроцессором.


103-110: Переход на graph.getExitPoint() и упрощение входящих ребер — ок

Опора на неизменяемую exit-точку графа и явный сбор источников входящих ребер к ней упрощает логику и устраняет прежние Optional-проверки. Выбор .toList() заранее стабилизирует набор.

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

36-41: toString() с диапазонами строк — полезно для диагностики

Представление вида Class{start:stop} улучшает трассировку ошибок связности графа. Выгодно в связке с edgePresentation.


43-57: Проверка дублирования исходящих ребер по типу — уточнить инварианты

Базовая проверка запрещает более одного исходящего ребра одного типа от любой вершины. Это хорошо соответствует условным вершинам (TRUE/FALSE), но требует подтверждения, что для всех прочих вершин (например, базовых блоков при сложных конструкциях) действительно недопустимы два DIRECT-ребра к разным целям.

Если есть допустимые случаи нескольких DIRECT-ребер, логику стоит перенести в специализированные подклассы (ConditionalVertex, ExitVertex и т.п.) или сужать проверку в базовом классе.

Для подстраховки предлагаю проверить, нет ли обходов базовой проверки через двухаргументный addEdge (см. комментарий в ControlFlowGraph.java ниже).

src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java (8)

51-57: Уточненные проверки размеров и связности — читабельно и по делу

Переход на hasSize/isEmpty и явная проверка одного исходящего ребра делают тест прозрачнее.


79-89: Корректная проверка ветвления и маршрутов в простом условии

Ожидание двух маршрутов после ветвления и пустого набора маршрутов в Exit согласуется с новой логикой ControlFlowGraphWalker.


268-282: Проверка ребер при break/мертвом коде — хорошее покрытие угла

Ассерты по количеству входящих в конец второго цикла, наличию DIRECT-ребра “Прервать” и сохранности ребер из “мертвого” участка помогают зафиксировать инварианты билдера CFG.


381-385: Переход на .toList() и метод-референс — аккуратная модернизация

Мелкое улучшение читаемости без изменения семантики.


509-526: Верхнеуровневый препроцессор ведет к ветвлению — тест отражает требование

producePreprocessorConditions(true) ожидаемо приводит к ветвлению на старте. Тест фиксирует инвариант.


528-548: Игнор препроцессора в секции переменных — верное поведение

Отсутствие ветвления при верхнеуровневом препроцессоре в секции переменных подтверждено тестом. Это важно для стабильности CFG.


550-573: Препроцессор в теле процедуры первым узлом — корректно обрабатывается

Тест полезен: ветвление есть, true-ветка ведет к первому оператору.


575-625: Сложный сценарий с возвратами/исключением под препроцессором покрыт хорошо

Ассерты на достижение exit-точки из обеих веток и на отсутствие лишних связей у узла препроцессора надежно закрепляют поведение билдера CFG.

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

34-40: Неизменяемая exit-точка, регистрируемая в конструкторе — плюс к устойчивости графа

Инициализация exitPoint и добавление вершины в граф в конструкторе устраняют неопределенность и упрощают потребителям доступ к выходу.


46-52: Перехват добавления ребра через onConnectOutgoing — правильное место для инвариантов

Переопределение addEdge(CfgVertex, CfgVertex, CfgEdge) с вызовом source.onConnectOutgoing закрепляет единый вход для проверок связности и типов ребер.


46-52: Проверка двухаргументных вызовов addEdge

Ни одного двухаргументного вызова addEdge(V, V) вне класса ControlFlowGraph не найдено — все вызовы используют трёхаргументную версию и корректно проходят через onConnectOutgoing. Инварианты сохраняются.

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

394-402: Топ‑уровневый препроцессор: корректная инициализация начала блока

Создание вершины для begin текущего блока при обработке верхнеуровневого препроцессора решает проблему отсутствия явного CodeBlock-входа. Флаг сбрасывается своевременно.


406-418: Перестройка препроцессорного if: выделение true-блока и маркер — ок

Создание отдельного блока для true-ветки и помещение маркерного узла (PreprocessorConditionVertex) в BuildParts обеспечивает корректную связку с последующими elseif/else/endif.


128-156: visitIfStatement: использование enterBlock() для текущего уровня — верное упрощение

Получение currentLevelBlock напрямую из enterBlock() упрощает логику и делает дальнейшие операции с BuildParts более очевидными.


170-173: Удаление пустых хвостов в if: корректно и безопасно

Проверка на отсутствие значимых ребер и пустой список statements c pattern matching — хороший способ не засорять граф лишними узлами.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java (2)

44-58: Добавить precondition: walker должен быть запущен (start())

Сейчас при вызове walkNext() без start() будет NPE внутри outgoingEdgesOf. Предлагаю явную проверку, чтобы сообщение об ошибке было предсказуемым.

Применить в этом методе:

   public CfgEdge walkNext() {
+    ensureStarted();
     var edges = availableRoutes();

И аналогично в availableRoutes() и walkNext(CfgEdgeType) (см. ниже).

Добавить вспомогательный метод в класс:

// вне выбранного диапазона, вставить в класс
private void ensureStarted() {
  if (currentNode == null) {
    throw new IllegalStateException("Walker is not started. Call start() first.");
  }
}

60-71: Симметрия и явная диагностика: проверять уникальность и для типизированных рёбер

Граф теперь сам запрещает дубликаты, но для симметрии с walkNext() и более явной диагностики багов графа лучше применить тот же подход в walkNext(CfgEdgeType) и уточнить текст ошибки.

-  public CfgEdge walkNext(CfgEdgeType edgeType) {
-    var edgeOrNot = availableRoutes().stream()
-      .filter(x -> x.getType() == edgeType)
-      .findAny();
-
-    if (edgeOrNot.isPresent()) {
-      currentNode = graph.getEdgeTarget(edgeOrNot.get());
-      return edgeOrNot.get();
-    }
-
-    throw new IllegalStateException("Edge is not found for node " + currentNode);
-  }
+  public CfgEdge walkNext(CfgEdgeType edgeType) {
+    ensureStarted();
+    var edgeOrNot = availableRoutes().stream()
+      .filter(x -> x.getType() == edgeType)
+      .reduce((CfgEdge a, CfgEdge b) -> {
+        throw new IllegalStateException("Multiple " + edgeType + " outgoing edges in " + currentNode);
+      });
+
+    if (edgeOrNot.isPresent()) {
+      currentNode = graph.getEdgeTarget(edgeOrNot.get());
+      return edgeOrNot.get();
+    }
+
+    throw new IllegalStateException("Edge of type " + edgeType + " is not found for node " + currentNode);
+  }

Дополнительно предложено уточнение сообщения об отсутствии ребра: включить тип ребра в текст.

src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java (3)

513-531: Усилить проверку топового препроцессора

Сейчас проверяем только факт ветвления. Рекомендую дополнить проверкой точного числа исходящих рёбер и их типов — это повысит надежность теста.

   walker.start();
 
-  assertThat(walker.isOnBranch()).isTrue();
+  assertThat(walker.isOnBranch()).isTrue();
+  assertThat(walker.availableRoutes()).hasSize(2);
+  assertThat(walker.availableRoutes())
+    .extracting(CfgEdge::getType)
+    .containsExactlyInAnyOrder(CfgEdgeType.TRUE_BRANCH, CfgEdgeType.FALSE_BRANCH);

532-553: Уточнить ожидаемое стартовое положение

Чтобы зафиксировать, что препроцессор в разделе переменных игнорируется, можно явно проверить первую операцию.

   walker.start();
 
-  assertThat(walker.isOnBranch()).isFalse();
+  assertThat(walker.isOnBranch()).isFalse();
+  assertThat(textOfCurrentNode(walker)).isEqualTo("А=8");

554-578: Топовый препроцессор в теле модуля — добавить проверку численности исходящих

Для полноты картины полезно проверить, что у узла-препроцессора два исходящих ребра.

   walker.start();
 
-  assertThat(walker.isOnBranch()).isTrue();
+  assertThat(walker.isOnBranch()).isTrue();
+  assertThat(walker.availableRoutes()).hasSize(2);
   walker.walkNext(CfgEdgeType.TRUE_BRANCH);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7cd23a7 and 0e367e4.

📒 Files selected for processing (5)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java (9 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgVertex.java (1 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraph.java (2 hunks)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java (2 hunks)
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgVertex.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraph.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java (1)
src/test/java/com/github/_1c_syntax/bsl/languageserver/util/TestUtils.java (1)
  • TestUtils (37-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (21, ubuntu-latest)
  • GitHub Check: build (23, macOS-latest)
  • GitHub Check: build (23, ubuntu-latest)
  • GitHub Check: build (21, macOS-latest)
  • GitHub Check: build (17, ubuntu-latest)
  • GitHub Check: build (21, windows-latest)
  • GitHub Check: build (23, windows-latest)
  • GitHub Check: build (17, macOS-latest)
  • GitHub Check: build (17, windows-latest)
  • GitHub Check: build
  • GitHub Check: Analyse
  • GitHub Check: Benchmark
🔇 Additional comments (9)
src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphWalker.java (1)

48-50: Защита от дублирующих DIRECT-рёбер в walkNext — хорошо

Явная проверка через reduce с выбросом ISE при >1 DIRECT-ребре добавляет полезную гарантию поверх инвариантов графа.

src/test/java/com/github/_1c_syntax/bsl/languageserver/cfg/ControlFlowGraphBuilderTest.java (8)

27-41: Импорты для Mockito/AssertJ — корректно

Добавленные CommonToken, Assertions и статические mock/when используются по делу.


55-61: Переход на fluent-ассерты AssertJ — ок

hasSize вместо size-проверок повышает читабельность.


83-99: Явные проверки числа путей и входящих рёбер — ок

Покрывают инварианты развилки и выходной вершины.


273-286: Уточнение проверок входящих/исходящих рёбер во вложенных циклах — ок

Хорошо фиксируют корректность «break/continue» трассировки.


386-389: Лаконичнее со Stream API

Использование BasicBlockVertex.class::isInstance и Stream.toList() уместно.


579-629: Покрытие выхода через Возврат/Исключение — отлично

Проверки корректно привязаны к graph.getExitPoint() и фиксируют отсутствие лишних связей у входного узла препроцессора.


631-643: Дубликаты рёбер запрещены — тест валидирует контракт

Четко покрывает новый guard в графе.


645-661: Запрет DIRECT-ребра из ConditionalVertex — тест корректный

Мок контекста и ожидание FlowGraphLinkException соответствуют новым правилам.

@nixel2007
nixel2007 requested a review from Copilot August 20, 2025 19:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR removes a workaround patch that was implemented for issue #1774 and fixes the underlying Control Flow Graph (CFG) bug that caused the original problem. The changes enable proper handling of preprocessor conditions in the CFG builder and improve graph validation.

  • Fixes CFG bug that led to issue #1774 by properly handling preprocessor conditions
  • Removes workaround code and enables preprocessor conditions processing
  • Adds comprehensive validation and exception handling for CFG edge connections

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
AllFunctionPathMustHaveReturnDiagnostic.java Enables preprocessor conditions and simplifies exit node handling
AllFunctionPathMustHaveReturnDiagnosticTest.java Updates test case to use preprocessor directive instead of regular if statement
CfgBuildingParseTreeVisitor.java Major refactoring to properly handle top-level preprocessor conditions and fix graph building logic
ControlFlowGraph.java Adds edge validation hooks and utility methods
CfgVertex.java Adds connection validation and duplicate edge detection
ConditionalVertex.java Adds validation for proper branch edge types
ExitVertex.java Prevents outgoing edges from exit vertex
FlowGraphLinkException.java New exception class for CFG validation errors
ControlFlowGraphWalker.java Improves error handling for multiple direct edges
BasicBlockVertex.java Improves toString representation for empty blocks
ControlFlowGraphBuilderTest.java Extensive test additions and improvements for CFG functionality

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

if (hasNoSignificantEdges(blockTail)
&& blockTail instanceof BasicBlockVertex basicBlock
&& basicBlock.statements().isEmpty()) {
graph.removeVertex(basicBlock);

Copilot AI Aug 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing a vertex from the graph while iterating over its edges or vertices can cause concurrent modification issues. The vertex should be marked for removal and cleaned up after the iteration completes.

Copilot uses AI. Check for mistakes.
Comment thread src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/ConditionalVertex.java Outdated
@sonarqubecloud

Copy link
Copy Markdown

@nixel2007
nixel2007 merged commit 9b7df1a into 1c-syntax:develop Aug 20, 2025
22 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 21, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants