Detect Redundant File Opens and Closes in COBOL

How to Detect Redundant File Opens and Closes in COBOL: A Static Analysis Approach

File handling in COBOL is deceptively simple on the surface. You declare a file in the ENVIRONMENT DIVISION, describe its record layout in the DATA DIVISION, and use OPEN, READ or WRITE, and CLOSE in the PROCEDURE DIVISION. Four verbs, one lifecycle. The problem is that COBOL programs rarely stay simple, they accumulate logic, exception paths, conditional branches, and PERFORM calls over decades of maintenance. What begins as a clean single-open, single-close pattern quietly mutates into something more complex: files opened twice in a single execution path, CLOSE statements that run when no file is open, error exits that branch past the CLOSE entirely.

None of these problems are immediately visible. The program compiles. It often runs without error, on the inputs you test. It fails silently on input combinations you never anticipated, or degrades batch window performance so gradually that nobody connects the cause to the effect. Static analysis finds them before any of that happens.

Leak Detection Across Every Language

SMART TS XL maps long-lived reference chains and unbounded data structures across your full codebase.

More Info

The Four File State Defects Worth Finding

Before getting into detection mechanics, here is a clear taxonomy of what you are looking for. Not all COBOL file problems are created equal.

DefectWhat Happens at RuntimeSeverity
Double OPENOPEN on an already-open file → FILE STATUS 41 or abendHigh
CLOSE without prior OPENCLOSE on a file that was never opened → FILE STATUS 42 or abendHigh
Missing CLOSE on normal exitFile left open at STOP RUN → OS closes it, but unflushed buffers risk data lossMedium
Missing CLOSE on error exitProgram abends with file open → potential record corruption on VSAM filesHigh
Redundant OPEN/CLOSE in a loopFile opened and closed on every iteration → severe I/O overheadMedium-High

Key takeaway: The highest-severity defects, double OPEN and CLOSE without OPEN, produce immediate runtime failures or data integrity risks. The performance defects (loop-level open/close) produce no error at all, just slow batch jobs that nobody has traced to the cause.

What Makes This Hard to Find Manually

The challenge is not identifying the pattern. Any developer knows you should not open a file twice. The challenge is that COBOL control flow is non-linear in ways that make manual tracing unreliable.

Consider a program with this structure:

cobol

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           PERFORM INITIALIZATION
           PERFORM PROCESS-RECORDS
           PERFORM TERMINATION
           STOP RUN.

       INITIALIZATION.
           OPEN INPUT CUSTOMER-FILE
           OPEN OUTPUT REPORT-FILE
           MOVE 0 TO WS-ERROR-FLAG.

       PROCESS-RECORDS.
           READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
               AT END MOVE 1 TO WS-EOF-FLAG
           END-READ
           IF WS-ERROR-FLAG = 1
               PERFORM ERROR-EXIT
           END-IF
           PERFORM UNTIL WS-EOF-FLAG = 1
               PERFORM PROCESS-ONE-RECORD
               READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
                   AT END MOVE 1 TO WS-EOF-FLAG
               END-EXEC
           END-PERFORM.

       ERROR-EXIT.
           DISPLAY 'Error in processing'
           CLOSE CUSTOMER-FILE    *> CLOSE here...
           STOP RUN.

       TERMINATION.
           CLOSE CUSTOMER-FILE    *> ...and CLOSE here too
           CLOSE REPORT-FILE
           STOP RUN.

Can you spot the defect without tracing every execution path? If WS-ERROR-FLAG is set to 1 inside PROCESS-ONE-RECORD (perhaps by a called subprogram), control flows to ERROR-EXIT, which closes the file and stops. That is correct. But if WS-ERROR-FLAG is not set, the loop completes, PROCESS-RECORDS returns, and TERMINATION runs, which also closes CUSTOMER-FILE. Two closes, one open. Runtime FILE STATUS 42.

This is a simple three-section program. Real programs have twenty sections, conditional PERFORM calls, GO TO statements in legacy code, and exception-handling routines that themselves call other routines. Manual tracing is not just error-prone, it is not tractable.


How Static Analysis Models File State

Static analysis detects these defects by constructing a control flow graph of the program and propagating file state along every path.

The analysis assigns each file a state variable with three possible values:

  • CLOSED, file has not been opened, or was successfully closed
  • OPEN, file has been opened and not yet closed
  • UNKNOWN, the state cannot be determined statically (e.g., conditional open in a branch not fully analyzed)

At each OPEN statement, the analysis checks: is this file already in state OPEN? If yes → double OPEN defect.

At each CLOSE statement: is this file in state CLOSED or UNKNOWN? If CLOSED → close without open defect.

At each path to STOP RUN or EXIT PROGRAM: is any file still in state OPEN? If yes → missing close defect.

For loops, the analysis checks whether an OPEN or CLOSE statement is dominated by a loop header, meaning it executes every iteration.

Watch out: The hardest cases for any analyzer are PERFORM calls into shared routines. If ERROR-HANDLER is called from twelve different places in a program, the file state at the point of the PERFORM call determines whether closing inside ERROR-HANDLER is correct or erroneous. An analyzer that does not propagate state through PERFORM chains will produce false negatives (missing real defects) or false positives (flagging correct code).


The Loop-Level OPEN/CLOSE: A Performance Defect With No Error Code

This pattern produces no runtime error and no FILE STATUS anomaly. It simply runs slowly, potentially orders of magnitude slower than necessary.

cobol

      *> PROBLEMATIC: file opened and closed on every iteration
       PROCESS-ALL-REGIONS.
           PERFORM VARYING WS-REGION-ID FROM 1 BY 1
               UNTIL WS-REGION-ID > 10
               OPEN INPUT CUSTOMER-FILE
               PERFORM PROCESS-REGION-RECORDS
               CLOSE CUSTOMER-FILE
           END-PERFORM.

<cite index=”20-1″>Unless the file is physically partitioned by region, this approach causes unnecessary overhead. In practice, it would be better to open the file once, read all records, and apply filtering in-memory or through logic.</cite>

Each OPEN on a VSAM file involves a catalog lookup, ACB initialization, and buffer pool allocation. Each CLOSE flushes buffers, updates the catalog, and releases the ACB. For a file with ten regions, this happens ten times instead of once. For a file with 10,000 records and a thousand regions, the math becomes painful.

cobol

      *> CORRECT: open once, filter inside the loop
       PROCESS-ALL-REGIONS.
           OPEN INPUT CUSTOMER-FILE
           PERFORM VARYING WS-REGION-ID FROM 1 BY 1
               UNTIL WS-REGION-ID > 10
               PERFORM PROCESS-REGION-RECORDS
           END-PERFORM
           CLOSE CUSTOMER-FILE.

Static analysis detects this by identifying OPEN and CLOSE statements that are loop-dominated, that is, every execution of the loop body executes the OPEN or CLOSE. The detection requires the control flow graph to represent loop structure, not just statement sequence.


FILE STATUS: Your Program’s File State Signal, If You Check It

Every COBOL file operation sets the FILE STATUS field defined in the FILE-CONTROL entry. A program that checks FILE STATUS after every OPEN, READ, WRITE, and CLOSE can detect and respond to every file state anomaly at runtime. A program that ignores FILE STATUS is flying blind.

Checklist: FILE STATUS codes every COBOL developer should know

  • 00, Successful completion
  • 10, End of file (READ: no more records)
  • 35, File not found (OPEN INPUT: file does not exist)
  • 41, File already open (OPEN on a file in OPEN state)
  • 42, File not open (CLOSE or READ on a file in CLOSED state)
  • 47, READ attempted on file not opened INPUT or I-O
  • 48, WRITE attempted on file not opened OUTPUT, I-O, or EXTEND
  • 97, File opened successfully (IBM-specific, some environments)

cobol

       FILE-CONTROL.
           SELECT CUSTOMER-FILE
               ASSIGN TO CUSTFILE
               FILE STATUS IS WS-CUST-FILE-STATUS.

       WORKING-STORAGE SECTION.
       01 WS-CUST-FILE-STATUS   PIC XX.

       PROCEDURE DIVISION.
           OPEN INPUT CUSTOMER-FILE
           IF WS-CUST-FILE-STATUS NOT = '00'
               DISPLAY 'OPEN failed: ' WS-CUST-FILE-STATUS
               PERFORM ABEND-ROUTINE
           END-IF.

Watch out: A common maintenance error is declaring FILE STATUS in the FILE-CONTROL entry but never referencing WS-CUST-FILE-STATUS in the PROCEDURE DIVISION. The field is populated after every file operation, but if no code checks it, the program runs past errors silently. Static analysis can flag this pattern: FILE STATUS declared but never referenced in conditional logic.


Three Real-World COBOL Patterns That Cause Defects

Pattern 1: The Shared Error-Exit Problem

A program has multiple processing sections, each with its own error handler. Several error handlers close the file before returning. The normal termination routine also closes the file. If an error occurs and recovery proceeds past the error handler, the file is closed twice.

cobol

       VALIDATE-RECORDS.
           READ CUSTOMER-FILE INTO WS-REC
               AT END MOVE 1 TO WS-EOF
           END-READ
           IF WS-CUST-FILE-STATUS NOT = '00' AND '10'
               CLOSE CUSTOMER-FILE    *> closes here on read error
               PERFORM WRITE-ERROR-LOG
               GO TO TERMINATION      *> skips to close in TERMINATION?
           END-IF.

       TERMINATION.
           CLOSE CUSTOMER-FILE        *> double close if GO TO reached here
           CLOSE REPORT-FILE
           STOP RUN.

The GO TO TERMINATION transfers control directly into TERMINATION, which executes its own CLOSE CUSTOMER-FILE. FILE STATUS 42.

Fix: Use a single centralized close routine. Every exit path calls the same routine, which checks whether the file is open before closing.

Pattern 2: The Conditional OPEN

A file is opened conditionally, only when a certain processing mode is active. But the CLOSE is unconditional, occurring in every execution path including those where the file was never opened.

cobol

       INITIALIZATION.
           IF WS-PROCESSING-MODE = 'FULL'
               OPEN OUTPUT AUDIT-FILE   *> only opened in FULL mode
           END-IF.

       TERMINATION.
           CLOSE AUDIT-FILE    *> ALWAYS closes -- FILE STATUS 42 in PARTIAL mode
           STOP RUN.

This defect is invisible in testing if tests always run in FULL mode. It surfaces in production on the first PARTIAL mode run.

Pattern 3: PERFORM Across Compilation Units

In programs that use CALL to invoke subprograms, a file opened in the main program may be passed to a subprogram that closes it, then the main program closes it again.

cobol

      *> Main program
       CALL 'SUBPROG1' USING CUSTOMER-FILE-STATUS
       *> SUBPROG1 closes CUSTOMER-FILE internally
       CLOSE CUSTOMER-FILE    *> double close

This pattern requires interprocedural analysis, tracing file state across the CALL boundary into the subprogram’s behavior, which simple intra-program analysis cannot detect.


What a Static Analyzer Needs to Do This Well

Not all static analysis tools handle COBOL file state analysis with equal depth. Here is a decision checklist for evaluating capability:

Does the tool handle PERFORM chains? COBOL programs use PERFORM to call named paragraphs and sections. File operations inside a PERFORM target must be visible to the caller’s state tracking.

Does the tool handle inline PERFORM VARYING (loops)? Loop detection is required to identify loop-dominated OPEN/CLOSE operations.

Does it track state across GO TO? Legacy COBOL programs use GO TO extensively. An analyzer that cannot follow GO TO edges in the control flow graph will miss entire classes of defects.

Does it handle COPY and REPLACE? File-handling code is often in COPY members. An analyzer that does not expand COPY members before analysis will miss operations defined in included code.

Does it check FILE STATUS usage? A complete analysis checks not just whether FILE STATUS is declared but whether it is actually checked after each operation.

Does it perform interprocedural analysis? CALLed subprograms that perform file operations create cross-program state dependencies. True coverage requires following CALL chains.

Key takeaway: A tool that only checks within a single paragraph or section will miss most real-world defects. The defects that matter in production, the ones that took years to appear and hours to diagnose, are the ones that span multiple sections, conditional branches, and called routines.


What to Do With the Results: A Prioritization Framework

When static analysis surfaces file defects, not all findings are equal. Here is how to triage them:

Fix immediately (before next batch run):

  • Double OPEN in any execution path where FILE STATUS 41 would cause an abend
  • CLOSE without OPEN in paths that execute in production
  • Missing CLOSE on error exit paths for VSAM files (write-integrity risk)

Schedule for next sprint:

  • Loop-dominated OPEN/CLOSE with measurable batch window impact
  • Missing FILE STATUS checks on critical files (audit, transaction, report)
  • Conditional OPEN with unconditional CLOSE in multi-mode programs

Track and address during next modernization pass:

  • Missing FILE STATUS declarations
  • CLOSE on normal exit paths only (OS handles it, but bad practice)
  • Interprocedural defects where the fix requires coordinating changes across multiple programs

How SMART TS XL Analyzes COBOL File State

SMART TS XL’s static code analysis builds a control flow model of every COBOL program in the environment, expanding COPY members, following PERFORM chains, tracing GO TO edges, and propagating file state through every branch of every conditional. It is not a pattern-matching scan; it is a structural model of what the program actually does at every reachable point in its execution.

The application dependency mapping capability extends this to the interprocedural dimension: when a COBOL program CALLs a subprogram that performs file operations, the dependency map represents that relationship, enabling analysis that crosses compilation unit boundaries. Double OPEN defects that span a main program and a called subprogram are visible in the structural model in a way they are not in any intra-program analysis.

The enterprise search capability makes the results actionable at scale: find every program in the portfolio that opens a specific dataset, every COPY member that contains a CLOSE statement, every program that has a PERFORM target containing an OPEN, across millions of lines of COBOL in seconds. For a modernization team preparing to migrate a batch workload to cloud, this search capability turns a weeks-long manual audit into a targeted query.

The JCL expansion capability adds the operational context: which JCL job steps invoke each program, which datasets each DD statement references, and how file handling in the COBOL code connects to the physical datasets defined in the JCL. A CLOSE defect on a file that JCL defines as a critical output dataset is higher priority than the same defect on a temporary work file, and that prioritization requires knowing the JCL context of each COBOL program.


The Broader Lesson: File State Is Just One Dimension

Redundant file operations are a specific instance of a more general problem: COBOL programs that have evolved over decades contain state-dependent behaviors, file state, switch states, counter states, that are only correctly understood by tracing every possible execution path through the full control flow graph.

Human review of this kind of code is slow, incomplete, and inconsistent. Different developers find different defects. The same developer reviewing the same program twice finds different defects. Static analysis is systematic: it applies the same rules to every path in every program, every time, without fatigue or assumption.

For teams managing large COBOL portfolios, whether for ongoing maintenance, for legacy modernization programs, or for compliance audits, the payoff of systematic file state analysis is not just the defects found. It is the confidence that the portfolio has been analyzed completely, and that the defects that remain are known rather than hidden.