| 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. |
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.
- The LFortran repository is the current working directory.
- The
lfconda/pixi environment is available at/Users/ondrej/.pixi/envs/lf/. lfortranon PATH points tosrc/bin/lfortran(the latest in-tree build).ninjais the build tool (in-tree build in the repo root).
Ask the user for (if not already provided):
- Test name — the name of the integration test that has
NO_DETECT_LEAK(e.g.,functions_36,arrays_34).
- NEVER modify the test
.f90file(s). Tests are sacrosanct. - There are exactly two outcomes:
- USER_LEAK: The Fortran source code itself leaks (e.g., user allocates a
pointer and never deallocates). Append
# USER_LEAKcomment to theRUN(...)line inCMakeLists.txtand keepNO_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_LEAKlabel from theRUN(...)line.
- USER_LEAK: The Fortran source code itself leaks (e.g., user allocates a
pointer and never deallocates). Append
- If you cannot determine the cause or fix it, stop and report findings to the user — do NOT guess.
- The
--detect-leaksflag instruments the compiled program with a memory tracker that reports unfreed allocations at program exit. - In
integration_tests/CMakeLists.txt, theRUN(...)macro checks theDETECT_LEAKCMake variable. If set andNO_DETECT_LEAKis NOT present on the test, it appends--detect-leaksto the lfortran invocation. - To run a specific test with leak detection manually:
If there is a leak, the output will include a memory leak report showing unfreed allocations.
lfortran --detect-leaks integration_tests/<test>.f90 ./a.out
The Fortran source code itself causes the leak. Common patterns:
- A
pointerisallocated but neverdeallocated 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.
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 generationsrc/lfortran/semantics/— semantic analysis
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.
- Find the test file(s):
grep -n "RUN(NAME <test_name>" integration_tests/CMakeLists.txt - Read the test
.f90file to understand what it does. - 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
- Capture the leak report output. Note the number of leaks and allocation sizes.
Determine whether this is a user leak or a compiler leak by examining BOTH the Fortran source AND the ASR output.
Read the .f90 file carefully and look for:
allocate(...)calls onpointervariables with no matchingdeallocate- 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.
Generate the ASR to see what the compiler produces:
lfortran --show-asr --no-color <test_name>.f90 2>&1 | head -500Look for:
- Temporary variables introduced by passes (names like
__lcompilers_*or compiler-generated names) Allocatenodes for temporaries that lack matchingExplicitDeallocateAssociate/SubroutineCallpatterns where temporaries are created for argument passing
If compiler-generated temporaries are allocated without corresponding deallocations, this is a compiler leak.
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 -500Search the pass source code for the temporary variable name pattern to find where it is created.
If this is a user leak:
-
Edit
integration_tests/CMakeLists.txtto append# USER_LEAKto theRUN(...)line. Keep theNO_DETECT_LEAKlabel 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 -
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. -
Skip to Phase 6 (Summary).
If this is a compiler leak:
- Identify the ASR pass or codegen code responsible for creating the un-freed temporary.
- Add the missing
ExplicitDeallocate(or equivalent cleanup) in the appropriate pass. Follow the pattern of existing cleanup code nearby. - Rebuild:
export PATH="/Users/ondrej/.pixi/envs/lf/bin:$PATH" ninja
- Re-run the test with leak detection to verify the fix:
cd integration_tests lfortran --detect-leaks <test_name>.f90 ./a.out
- The leak report should now show zero leaks.
- If the fix doesn't work, iterate: re-diagnose, adjust, rebuild, and re-test.
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 ...)
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.
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 diffto ensure updates are correct.
-
Stage all changes:
git add -A
-
Review:
git diff --cached --stat
-
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. -
Do NOT include
Co-authored-bytrailers.
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.
- 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
Allocatefor a temporary, there should be a matchingExplicitDeallocateon every control-flow path. A missing one in anelsebranch 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_LEAKeven if there are also compiler leaks (fix the compiler leaks anyway, but keepNO_DETECT_LEAKwith theUSER_LEAKcomment). - Modfile issues: If you see "Incompatible format: LFortran Modfile...",
run
ninja clean && ninja.