[flang-commits] [flang] [flang] Do not branch to a FORMAT statement from an assigned GO TO (PR #217220)

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Tue Aug 18 23:23:53 PDT 2026


https://github.com/ergawy created https://github.com/llvm/llvm-project/pull/217220

Label analysis already classifies which labeled statements may be named by a statement that branches.  Record the positions of those statements in the semantics context and consult it when lowering records the targets of an assigned GO TO, so that a FORMAT statement is not given a target block.

A GO TO whose variable holds only a format label now reaches the run-time error instead of branching into the FORMAT statement.

Co-Authored-By: Claude

>From 1a2651b720ad7e7bb5dfd5e5e5dcee65a8dc2560 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Tue, 18 Aug 2026 23:21:17 -0700
Subject: [PATCH] [flang] Do not branch to a FORMAT statement from an assigned
 GO TO

Label analysis already classifies which labeled statements may be named by
a statement that branches.  Record the positions of those statements in the
semantics context and consult it when lowering records the targets of an
assigned GO TO, so that a FORMAT statement is not given a target block.

A GO TO whose variable holds only a format label now reaches the run-time
error instead of branching into the FORMAT statement.

Co-Authored-By: Claude
---
 flang/include/flang/Semantics/semantics.h     | 19 +++++
 flang/lib/Lower/PFTBuilder.cpp                | 20 ++++-
 flang/lib/Semantics/resolve-labels.cpp        | 14 ++++
 .../Lower/assigned-goto-format-target.f90     | 75 +++++++++++++++++++
 flang/test/Semantics/assign07.f90             |  6 ++
 5 files changed, 132 insertions(+), 2 deletions(-)
 create mode 100644 flang/test/Lower/assigned-goto-format-target.f90

diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index 28e5ccd9ad409..ad7abdddae1ef 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -377,6 +377,24 @@ class SemanticsContext {
   // Top-level ProgramTrees are owned by the SemanticsContext for persistence.
   ProgramTree &SaveProgramTree(ProgramTree &&);
 
+  // Label analysis classifies every labeled statement, and only some of those
+  // classifications may be named by a statement that branches.  Lowering needs
+  // the same distinction when it records the targets of a branch, so the
+  // positions of the statements that may be branched to are kept here rather
+  // than being derived a second time from the parse tree.
+  //
+  // A statement is identified by its source position, which is unique, because
+  // a label is only unique within one program unit.  Recorded by
+  // ValidateLabels(); querying it before that has run reports every statement
+  // as not a branch target.
+  void RecordLegalBranchTarget(parser::CharBlock statementPosition) {
+    legalBranchTargets_.insert(statementPosition);
+  }
+  bool IsLegalBranchTarget(parser::CharBlock statementPosition) const {
+    return legalBranchTargets_.find(statementPosition) !=
+        legalBranchTargets_.end();
+  }
+
 private:
   struct ScopeIndexComparator {
     bool operator()(parser::CharBlock, parser::CharBlock) const;
@@ -389,6 +407,7 @@ class SemanticsContext {
       const parser::CharBlock &, const Symbol &, parser::MessageFixedText &&);
   void CheckError(const Symbol &);
 
+  std::set<parser::CharBlock> legalBranchTargets_;
   const common::IntrinsicTypeDefaultKinds &defaultKinds_;
   const common::LanguageFeatureControl &languageFeatures_;
   const common::LangOptions &langOpts_;
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index e5c91ac101679..fcb7b8e000dcb 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -1049,11 +1049,27 @@ class PFTBuilder {
             // Mark every possible target of the assigned GO TO so that
             // wrappability analyses can see any escape from an enclosing
             // construct.
+            //
+            // Only statements that a branch may name are marked.  Marking any
+            // other statement would give it a block, and lowering picks the
+            // targets of the branch it generates by testing for one, so a
+            // program that branches to a statement that is not a branch
+            // target would jump into it instead of reaching the run-time
+            // error it is supposed to get.  Label analysis has already
+            // classified every labeled statement, so ask it rather than
+            // deriving the answer again from the parse tree.
+            auto markIfBranchTarget = [&](parser::Label label) {
+              lower::pft::Evaluation *target{
+                  labelEvaluationMap->find(label)->second};
+              assert(target && "missing branch target evaluation");
+              if (semanticsContext.IsLegalBranchTarget(target->position))
+                markBranchTarget(eval, *target);
+            };
             const auto &labelList = std::get<std::list<parser::Label>>(s.t);
             if (!labelList.empty()) {
               // Explicit target list: `go to v, (l1, l2, ...)`.
               for (const auto &label : labelList)
-                markBranchTarget(eval, label);
+                markIfBranchTarget(label);
             } else {
               // No explicit list (`go to v`): fall back to the set of labels
               // that have been previously ASSIGN'd to v.
@@ -1065,7 +1081,7 @@ class PFTBuilder {
                 auto iter = assignSymbolLabelMap->find(*sym);
                 if (iter != assignSymbolLabelMap->end())
                   for (auto label : iter->second)
-                    markBranchTarget(eval, label);
+                    markIfBranchTarget(label);
               }
             }
             eval.isUnstructured = true;
diff --git a/flang/lib/Semantics/resolve-labels.cpp b/flang/lib/Semantics/resolve-labels.cpp
index f36ec0b24bfc6..63d52179c43d9 100644
--- a/flang/lib/Semantics/resolve-labels.cpp
+++ b/flang/lib/Semantics/resolve-labels.cpp
@@ -1116,6 +1116,19 @@ void CheckScopeConstraints(const SourceStmtList &stmts,
   }
 }
 
+// Record the statements that a branch may name, for lowering to consult when
+// it records the targets of a branch.  Statements are identified by source
+// position because a label is only unique within one program unit.
+static void RecordLegalBranchTargets(
+    const TargetStmtMap &labels, SemanticsContext &context) {
+  for (const auto &[label, info] : labels) {
+    if (info.labeledStmtClassificationSet.test(TargetStatementEnum::Branch) ||
+        info.labeledStmtClassificationSet.test(
+            TargetStatementEnum::CompatibleBranch))
+      context.RecordLegalBranchTarget(info.parserCharBlock);
+  }
+}
+
 void CheckBranchTargetConstraints(const SourceStmtList &stmts,
     const TargetStmtMap &labels, SemanticsContext &context) {
   for (const auto &stmt : stmts) {
@@ -1229,6 +1242,7 @@ bool CheckConstraints(ParseTreeAnalyzer &&parseTreeAnalysis) {
     CheckDataTransferConstraints(dataTransfers, labels, scopes, context);
     const auto &assigns{programUnit.assignStmtSources};
     CheckAssignConstraints(assigns, labels, scopes, context);
+    RecordLegalBranchTargets(labels, context);
   }
   return !context.AnyFatalError();
 }
diff --git a/flang/test/Lower/assigned-goto-format-target.f90 b/flang/test/Lower/assigned-goto-format-target.f90
new file mode 100644
index 0000000000000..4d502738f9830
--- /dev/null
+++ b/flang/test/Lower/assigned-goto-format-target.f90
@@ -0,0 +1,75 @@
+! RUN: bbc -emit-fir -o - %s | FileCheck %s
+
+! A FORMAT statement is not a branch target.  Branching to a label that was
+! ASSIGN'd from one is not conforming, and the program is meant to reach the
+! run-time error rather than jump into the FORMAT statement.
+
+! The only label assigned to j is a FORMAT, so no target survives and no
+! branch is generated at all.
+! CHECK-LABEL: func.func @_QPfmt_only(
+! CHECK:         %[[J:.*]] = fir.declare %arg0
+! CHECK:         fir.store %c1{{.*}} to %[[J]]
+! CHECK-NOT:     fir.select
+! CHECK-NOT:     ^bb
+! CHECK:         fir.call @_FortranAReportFatalUserError
+! CHECK-NEXT:    fir.unreachable
+subroutine fmt_only(j)
+  integer :: j
+  assign 1 to j
+  go to j
+1 format("fmt")
+end subroutine
+
+! Both labels are assigned to j, but only 20 is a branch target.  The select
+! carries exactly one case, for 20; label 1 does not appear.
+! CHECK-LABEL: func.func @_QPfmt_and_real(
+! CHECK:         %[[J:.*]] = fir.declare %arg0
+! CHECK:         fir.store %c1{{.*}} to %[[J]]
+! CHECK:         fir.store %c20{{.*}} to %[[J]]
+! CHECK:         %[[V:.*]] = fir.load %[[J]]
+! CHECK:         fir.select %[[V]] : i32 [20, ^bb[[TGT:[0-9]+]], unit, ^bb[[ERR:[0-9]+]]]
+!
+! The default destination reports the error and terminates.
+! CHECK:       ^bb[[ERR]]:
+! CHECK:         fir.call @_FortranAReportFatalUserError
+! CHECK-NEXT:    fir.unreachable
+!
+! The one real target is the PRINT at label 20, which returns normally.
+! CHECK:       ^bb[[TGT]]:
+! CHECK:         fir.call @_FortranAioBeginExternalListOutput
+! CHECK:         return
+subroutine fmt_and_real(j)
+  integer :: j
+  assign 1 to j
+  assign 20 to j
+  go to j
+1 format("fmt")
+20 print *, "twenty"
+end subroutine
+
+! FORMAT is the only labelled statement that can be assigned and then reach the
+! GO TO without being a branch target; every other kind is rejected by semantic
+! analysis at the ASSIGN.  The labels below are branch targets of four different
+! kinds -- an action statement, the statement that begins an IF construct, the
+! statement that begins a DO construct, and the END statement of the subroutine
+! -- so every one of them survives and appears as a case of the select.  Each is
+! branched to from the same inclusive scope, and the two construct labels name
+! the statement that begins the construct rather than the one that ends it, so
+! control enters the construct normally instead of jumping into its interior.
+! CHECK-LABEL: func.func @_QPbranch_target_kinds(
+! CHECK:         fir.select %{{.*}} : i32 [10, ^bb{{[0-9]+}}, 20, ^bb{{[0-9]+}}, 30, ^bb{{[0-9]+}}, 40, ^bb{{[0-9]+}}, unit, ^bb{{[0-9]+}}]
+subroutine branch_target_kinds(n)
+  integer :: n, j
+  assign 10 to j
+  assign 20 to j
+  assign 30 to j
+  assign 40 to j
+  go to j
+10 continue
+20 if (n == 1) then
+     print *, "a"
+   end if
+30 do while (n > 0)
+     n = n - 1
+   end do
+40 end subroutine
diff --git a/flang/test/Semantics/assign07.f90 b/flang/test/Semantics/assign07.f90
index 41a99c1d5d0a7..5b522d170b37c 100644
--- a/flang/test/Semantics/assign07.f90
+++ b/flang/test/Semantics/assign07.f90
@@ -26,10 +26,16 @@ subroutine test(n)
     if (n==1) goto lab(1,666)
     !ERROR: Label '2' was not found
     if (n==1) goto lab(1,2)
+    ! Label 3 is a FORMAT statement in this scope.  It can be assigned and
+    ! used as a format, but naming it in the label list of an assigned GOTO
+    ! is an error: a FORMAT statement is not a branch target.  The diagnostic
+    ! is reported on the FORMAT statement itself, below.
+    if (n==1) goto lab(1,3)
     assign 3 to lab
     write(*,fmt=lab) ! ok
     write(*,fmt=implicitlab3) ! ok
 1   continue
+    !ERROR: Label '3' is not a branch target
 3   format('yes')
   end subroutine test
 end program



More information about the flang-commits mailing list