From 1473370df73736c9405cb938e153230761be0fbb Mon Sep 17 00:00:00 2001 From: Jakub Benda Date: Fri, 19 Sep 2025 11:55:12 +0200 Subject: [PATCH] Fortran: Tolerate #include loops in dependency scanner The function `cmFortranParser_FilePush` was always processing the provided (included) file, even though it may have been processed before. This lead to infinite recursion in cases where the include guards were not properly interpreted, e.g. `# if !defined` instead of `#ifndef`. This commit introduces a cache of paths to already processed files. These files are now ignored in `cmFortranParser_FilePush` (treated as non-existing). Fixes: #27238 --- Source/cmFortranParser.h | 3 +++ Source/cmFortranParserImpl.cxx | 27 +++++++++++++++----------- Tests/FortranOnly/CMakeLists.txt | 6 ++++++ Tests/FortranOnly/test_include_loop.F | 3 +++ Tests/FortranOnly/test_include_loop.fh | 14 +++++++++++++ 5 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 Tests/FortranOnly/test_include_loop.F create mode 100644 Tests/FortranOnly/test_include_loop.fh diff --git a/Source/cmFortranParser.h b/Source/cmFortranParser.h index a835e197c8..c9e4e872c2 100644 --- a/Source/cmFortranParser.h +++ b/Source/cmFortranParser.h @@ -163,6 +163,9 @@ struct cmFortranParser_s // Lexical scanner instance. yyscan_t Scanner; + // List of full paths to already processed files. + std::set VisitedFilePaths; + // Stack of open files in the translation unit. std::stack FileStack; diff --git a/Source/cmFortranParserImpl.cxx b/Source/cmFortranParserImpl.cxx index 4c0d5dde46..08dab98326 100644 --- a/Source/cmFortranParserImpl.cxx +++ b/Source/cmFortranParserImpl.cxx @@ -87,17 +87,22 @@ std::string cmFortranParser_s::SModName(std::string const& mod_name, bool cmFortranParser_FilePush(cmFortranParser* parser, char const* fname) { - // Open the new file and push it onto the stack. Save the old - // buffer with it on the stack. - if (FILE* file = cmsys::SystemTools::Fopen(fname, "rb")) { - YY_BUFFER_STATE current = cmFortranLexer_GetCurrentBuffer(parser->Scanner); - std::string dir = cmSystemTools::GetParentDirectory(fname); - cmFortranFile f(file, current, dir); - YY_BUFFER_STATE buffer = - cmFortran_yy_create_buffer(nullptr, 16384, parser->Scanner); - cmFortran_yy_switch_to_buffer(buffer, parser->Scanner); - parser->FileStack.push(f); - return true; + // Do not revisit already processed files; treat them as non-existing. + // Avoids infinite recursion caused by misinterpreted include guards. + if (parser->VisitedFilePaths.insert(fname).second) { + // Open the new file and push it onto the stack. Save the old + // buffer with it on the stack. + if (FILE* file = cmsys::SystemTools::Fopen(fname, "rb")) { + YY_BUFFER_STATE current = + cmFortranLexer_GetCurrentBuffer(parser->Scanner); + std::string dir = cmSystemTools::GetParentDirectory(fname); + cmFortranFile f(file, current, dir); + YY_BUFFER_STATE buffer = + cmFortran_yy_create_buffer(nullptr, 16384, parser->Scanner); + cmFortran_yy_switch_to_buffer(buffer, parser->Scanner); + parser->FileStack.push(f); + return true; + } } return false; } diff --git a/Tests/FortranOnly/CMakeLists.txt b/Tests/FortranOnly/CMakeLists.txt index 7efbeb62d9..d181ad5f66 100644 --- a/Tests/FortranOnly/CMakeLists.txt +++ b/Tests/FortranOnly/CMakeLists.txt @@ -198,3 +198,9 @@ if(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_OFF AND set_property(SOURCE no_preprocess_source_upper.F no_preprocess_source_fpp.fpp PROPERTY Fortran_PREPROCESS OFF) endif() + +# Test that we are safe from infinite include recursion in dependency scanner +# (SunPro Fortran compiler does not support recursive inclusion) +if (NOT CMAKE_Fortran_COMPILER_ID STREQUAL "SunPro") + add_executable(test_include_loop test_include_loop.F) +endif() diff --git a/Tests/FortranOnly/test_include_loop.F b/Tests/FortranOnly/test_include_loop.F new file mode 100644 index 0000000000..732ecdb399 --- /dev/null +++ b/Tests/FortranOnly/test_include_loop.F @@ -0,0 +1,3 @@ + program test_include_loop +#include "test_include_loop.fh" + end program diff --git a/Tests/FortranOnly/test_include_loop.fh b/Tests/FortranOnly/test_include_loop.fh new file mode 100644 index 0000000000..4b9cf8364c --- /dev/null +++ b/Tests/FortranOnly/test_include_loop.fh @@ -0,0 +1,14 @@ +! CMake Fortran parser does not understand the `#if !defined(...)` +! syntax, so it does not guard against recursive inclusion. Unless +! the parser keeps track of already visited files, this leads to +! processing of around 2^N files, where N is the maximal number of +! open files. Because N is typically of the order of 1000, without +! bookkeeping this test hangs. + +#if !defined(test_include_loop_fh) +#define test_include_loop_fh + +#include "test_include_loop.fh" +#include "test_include_loop.fh" + +#endif