Skip to content

Instantly share code, notes, and snippets.

@certik
Created March 31, 2026 01:06
Show Gist options
  • Select an option

  • Save certik/65f469d39312b6d3d4c2a0eaef73b0c2 to your computer and use it in GitHub Desktop.

Select an option

Save certik/65f469d39312b6d3d4c2a0eaef73b0c2 to your computer and use it in GitHub Desktop.
fix-leak
name fix-leak
description Fix a memory leak flagged by NO_DETECT_LEAK in an LFortran integration test. Investigates whether the leak is a user-level Fortran pointer leak or a compiler-generated leak from an ASR pass. For user leaks, appends a USER_LEAK comment. For compiler leaks, fixes the source and removes the NO_DETECT_LEAK label. Triggers: fix leak, memory leak, NO_DETECT_LEAK, detect leak, leak, USER_LEAK, compiler leak.

Fix Leak — Resolve NO_DETECT_LEAK in an Integration Test

Given an integration test that has the NO_DETECT_LEAK label, investigate whether the memory leak is caused by user Fortran code or by the LFortran compiler (ASR passes / codegen). Then either mark it as a user leak or fix the compiler bug.

Prerequisites

  • The LFortran repository is the current working directory.
  • The lf conda/pixi environment is available at /Users/ondrej/.pixi/envs/lf/.
  • lfortran on PATH points to src/bin/lfortran (the latest in-tree build).
  • ninja is the build tool (in-tree build in the repo root).

Inputs to Gather

Ask the user for (if not already provided):

  1. Test name — the name of the integration test that has NO_DETECT_LEAK (e.g., functions_36, arrays_34).

Key Rules

  1. NEVER modify the test .f90 file(s). Tests are sacrosanct.
  2. There are exactly two outcomes:
    • USER_LEAK: The Fortran source code itself leaks (e.g., user allocates a pointer and never deallocates). Append # USER_LEAK comment to the RUN(...) line in CMakeLists.txt and keep NO_DETECT_LEAK.
    • Compiler fix: The leak is caused by an ASR→ASR pass or codegen generating temporaries/pointers that are not freed. Fix the compiler source, remove the NO_DETECT_LEAK label from the RUN(...) line.
  3. If you cannot determine the cause or fix it, stop and report findings to the user — do NOT guess.

Background: How Leak Detection Works

  • The --detect-leaks flag instruments the compiled program with a memory tracker that reports unfreed allocations at program exit.
  • In integration_tests/CMakeLists.txt, the RUN(...) macro checks the DETECT_LEAK CMake variable. If set and NO_DETECT_LEAK is NOT present on the test, it appends --detect-leaks to the lfortran invocation.
  • To run a specific test with leak detection manually:
    lfortran --detect-leaks integration_tests/<test>.f90
    ./a.out
    If there is a leak, the output will include a memory leak report showing unfreed allocations.

Background: User Leak vs Compiler Leak

User Leak (mark as USER_LEAK)

The Fortran source code itself causes the leak. Common patterns:

  • A pointer is allocated but never deallocated before program exit.
  • A pointer is re-associated (=>) after being allocated, orphaning the original allocation.
  • The program is correct Fortran — GFortran would also leak in the same way. This is the programmer's responsibility, not the compiler's.

Example: functions_18.f90 allocates type_1%x(3) then reassigns it via type_1%x => x_global, orphaning the allocation. That is user code leaking.

Compiler Leak (fix the compiler)

The LFortran compiler (usually an ASR→ASR pass) creates temporary variables, arrays, or pointers during compilation that are never freed. The user's Fortran code is clean — the leak is entirely generated by the compiler.

Example: commit 41c16d00291bc01362e2e66cbb60840f0ea56d0f fixed a leak in array_passed_in_function_call pass. When a non-contiguous array section was passed to a subroutine, the pass allocated a contiguous temporary for copy-in/copy-out but forgot to emit an ExplicitDeallocate in the non-contiguous cleanup branch.

Common compiler-leak locations:

  • src/libasr/pass/ — ASR transformation passes (most common source)
  • src/libasr/codegen/ — code generation
  • src/lfortran/semantics/ — semantic analysis

Procedure

Phase 0: Read Project Guidelines

Read the AGENTS.md file in the LFortran repository root to understand project conventions. Also read CLAUDE.md if it exists. Follow all instructions therein, but the instructions in this SKILL.md file take precedence.

Phase 1: Reproduce the Leak

  1. Find the test file(s):
    grep -n "RUN(NAME <test_name>" integration_tests/CMakeLists.txt
  2. Read the test .f90 file to understand what it does.
  3. Run the test with leak detection:
    export PATH="/Users/ondrej/.pixi/envs/lf/bin:$PATH"
    cd integration_tests
    lfortran --detect-leaks <test_name>.f90
    ./a.out
  4. Capture the leak report output. Note the number of leaks and allocation sizes.

Phase 2: Classify the Leak

Determine whether this is a user leak or a compiler leak by examining BOTH the Fortran source AND the ASR output.

Step 2a: Analyze the Fortran source

Read the .f90 file carefully and look for:

  • allocate(...) calls on pointer variables with no matching deallocate
  • Pointer associations (=>) that orphan previous allocations
  • Derived types with pointer components that are allocated but not cleaned up

If the Fortran source has obvious unmatched allocations of user-declared pointers, this is likely a USER_LEAK.

Step 2b: Examine the ASR

Generate the ASR to see what the compiler produces:

lfortran --show-asr --no-color <test_name>.f90 2>&1 | head -500

Look for:

  • Temporary variables introduced by passes (names like __lcompilers_* or compiler-generated names)
  • Allocate nodes for temporaries that lack matching ExplicitDeallocate
  • Associate / SubroutineCall patterns where temporaries are created for argument passing

If compiler-generated temporaries are allocated without corresponding deallocations, this is a compiler leak.

Step 2c: Cross-reference with passes

If you suspect a compiler leak, identify which pass creates the temporary:

# Show ASR before passes
lfortran --show-asr --skip-pass=all --no-color <test_name>.f90 2>&1 | head -500
# Compare with ASR after passes
lfortran --show-asr --no-color <test_name>.f90 2>&1 | head -500

Search the pass source code for the temporary variable name pattern to find where it is created.

Phase 3a: Handle USER_LEAK

If this is a user leak:

  1. Edit integration_tests/CMakeLists.txt to append # USER_LEAK to the RUN(...) line. Keep the NO_DETECT_LEAK label in place.

    Before:

    RUN(NAME test_name LABELS gfortran llvm ... NO_DETECT_LEAK)
    

    After:

    RUN(NAME test_name LABELS gfortran llvm ... NO_DETECT_LEAK) # USER_LEAK
    
  2. Commit with message:

    Mark <test_name> as USER_LEAK
    
    The test allocates a Fortran pointer that is never explicitly
    deallocated. This is a user-code leak, not a compiler bug.
    
  3. Skip to Phase 6 (Summary).

Phase 3b: Fix Compiler Leak

If this is a compiler leak:

  1. Identify the ASR pass or codegen code responsible for creating the un-freed temporary.
  2. Add the missing ExplicitDeallocate (or equivalent cleanup) in the appropriate pass. Follow the pattern of existing cleanup code nearby.
  3. Rebuild:
    export PATH="/Users/ondrej/.pixi/envs/lf/bin:$PATH"
    ninja
  4. Re-run the test with leak detection to verify the fix:
    cd integration_tests
    lfortran --detect-leaks <test_name>.f90
    ./a.out
  5. The leak report should now show zero leaks.
  6. If the fix doesn't work, iterate: re-diagnose, adjust, rebuild, and re-test.

Phase 4: Remove NO_DETECT_LEAK (Compiler Fix Only)

Edit integration_tests/CMakeLists.txt to remove the NO_DETECT_LEAK label from the test's RUN(...) line.

Before:

RUN(NAME test_name LABELS gfortran llvm ... NO_DETECT_LEAK)

After:

RUN(NAME test_name LABELS gfortran llvm ...)

Phase 5: Run Tests

Integration Tests

export PATH="/Users/ondrej/.pixi/envs/lf/bin:$PATH"
cd integration_tests
./run_tests.py -j16 --detect-leaks &> log_leaks
tail -n30 log_leaks
./run_tests.py -j16 &> log
tail -n30 log
  • If all tests pass (both with and without leaks), continue.
  • If any test fails, examine the log, fix regressions, rebuild, and re-run.
  • Both test runs must pass: with leaks and without leaks.

Reference Tests (Compiler Fix Only)

export PATH="/Users/ondrej/.pixi/envs/lf/bin:$PATH"
cd <lfortran-root>
./run_tests.py &> log
tail -n30 log
  • If reference tests fail because your fix changes compiler output, update references:
    ./run_tests.py -u
  • Review changes with git diff to ensure updates are correct.

Phase 6: Commit

  1. Stage all changes:

    git add -A
  2. Review:

    git diff --cached --stat
  3. Commit with an appropriate message:

    For a compiler fix:

    fix: free <description of what was leaking>
    
    The <pass_name> pass allocated a temporary <description> but did not
    emit a corresponding ExplicitDeallocate, causing a memory leak.
    
    Add the missing deallocation in <location>.
    
    Remove NO_DETECT_LEAK from <test_name>.
    

    For a user leak annotation:

    Mark <test_name> as USER_LEAK
    
    The test allocates a Fortran pointer that is never explicitly
    deallocated by the user code. This is not a compiler bug.
    
  4. Do NOT include Co-authored-by trailers.

Phase 7: Summary

Print a summary:

Leak investigation complete!

Test: <test_name>
Verdict: <USER_LEAK | Compiler fix>
Action: <what was done>
Files modified:
  <list of changed files>
Commit: <short SHA and first line>

All tests pass.

Tips

  • ASR dump is your best friend: comparing ASR before/after passes reveals exactly which pass introduces un-freed temporaries.
  • Common leaky passes: array_passed_in_function_call, implied_do_loops, array_op, intrinsic_function. These create temporaries that need explicit cleanup.
  • Look for symmetry: If a pass has an Allocate for a temporary, there should be a matching ExplicitDeallocate on every control-flow path. A missing one in an else branch or early-return path is a common bug.
  • Pointer vs Allocatable: Compiler temporaries are often pointers (not allocatables) so they can be associated without copying in the contiguous case. Non-contiguous copies MUST be freed explicitly.
  • Multiple leaks: A test may have BOTH user leaks and compiler leaks. If the user code itself leaks, it must be marked USER_LEAK even if there are also compiler leaks (fix the compiler leaks anyway, but keep NO_DETECT_LEAK with the USER_LEAK comment).
  • Modfile issues: If you see "Incompatible format: LFortran Modfile...", run ninja clean && ninja.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment