diff --git a/ApplicationLibCode/FileInterface/RifActiveCellsReader.cpp b/ApplicationLibCode/FileInterface/RifActiveCellsReader.cpp index 9a5396c7da..7fd2073b6a 100644 --- a/ApplicationLibCode/FileInterface/RifActiveCellsReader.cpp +++ b/ApplicationLibCode/FileInterface/RifActiveCellsReader.cpp @@ -93,6 +93,9 @@ std::vector> if ( indexToCell >= activeCellCount ) { indexToCell = poreValueIndex - activeCellCount; + + // An odd number of values has no matching cell for the last value + if ( indexToCell >= activeCellCount ) continue; } if ( porvValues[poreValueIndex] > 0.0 ) diff --git a/ApplicationLibCode/FileInterface/RifEclipseInputFileTools.cpp b/ApplicationLibCode/FileInterface/RifEclipseInputFileTools.cpp index 6100bb1b77..4eae1faf32 100644 --- a/ApplicationLibCode/FileInterface/RifEclipseInputFileTools.cpp +++ b/ApplicationLibCode/FileInterface/RifEclipseInputFileTools.cpp @@ -160,14 +160,39 @@ bool RifEclipseInputFileTools::openGridFile( const QString& fileName, RigEclipse } } + auto freeKeywords = [&]() + { + if ( specGridKw ) ecl_kw_free( specGridKw ); + if ( zCornKw ) ecl_kw_free( zCornKw ); + if ( coordKw ) ecl_kw_free( coordKw ); + if ( actNumKw ) ecl_kw_free( actNumKw ); + if ( mapAxesKw ) ecl_kw_free( mapAxesKw ); + }; + if ( specGridKw && zCornKw && coordKw ) { + if ( ecl_kw_get_size( specGridKw ) < 3 ) + { + RiaLogging::error( "SPECGRID must contain at least three values (nx, ny, nz)" ); + freeKeywords(); + return false; + } + int nx = ecl_kw_iget_int( specGridKw, 0 ); int ny = ecl_kw_iget_int( specGridKw, 1 ); int nz = ecl_kw_iget_int( specGridKw, 2 ); ecl_grid_type* inputGrid = ecl_grid_alloc_GRDECL_kw( nx, ny, nz, zCornKw, coordKw, actNumKw, mapAxesKw ); + // The grid is null if the keyword sizes do not match the dimensions given by SPECGRID + if ( !inputGrid ) + { + RiaLogging::error( + "Failed to create grid from GRDECL keywords. Check that the sizes of ZCORN, COORD and ACTNUM match SPECGRID." ); + freeKeywords(); + return false; + } + RifReaderEclipseOutput::transferGeometry( inputGrid, eclipseCase, false ); if ( readFaultData ) @@ -194,11 +219,7 @@ bool RifEclipseInputFileTools::openGridFile( const QString& fileName, RigEclipse eclipseCase->mainGrid()->setMapAxes( mapAxesValues ); } - ecl_kw_free( specGridKw ); - ecl_kw_free( zCornKw ); - ecl_kw_free( coordKw ); - if ( actNumKw ) ecl_kw_free( actNumKw ); - if ( mapAxesKw ) ecl_kw_free( mapAxesKw ); + freeKeywords(); ecl_grid_free( inputGrid ); diff --git a/ApplicationLibCode/FileInterface/RifPerforationIntervalReader.cpp b/ApplicationLibCode/FileInterface/RifPerforationIntervalReader.cpp index 0b40a14dd3..2b7f8aad01 100644 --- a/ApplicationLibCode/FileInterface/RifPerforationIntervalReader.cpp +++ b/ApplicationLibCode/FileInterface/RifPerforationIntervalReader.cpp @@ -113,6 +113,9 @@ void RifPerforationIntervalReader::readFileIntoMap( const QString& continue; } + // Four values are read starting at mdStartIndex + if ( parts.size() < mdStartIndex + 4 ) continue; + interval.startMD = parts[mdStartIndex].toDouble(); interval.endMD = parts[mdStartIndex + 1].toDouble(); interval.diameter = parts[mdStartIndex + 2].toDouble(); diff --git a/ApplicationLibCode/FileInterface/RifVtkSurfaceImporter.cpp b/ApplicationLibCode/FileInterface/RifVtkSurfaceImporter.cpp index 5a80daf9b7..1e6b6dfcdb 100644 --- a/ApplicationLibCode/FileInterface/RifVtkSurfaceImporter.cpp +++ b/ApplicationLibCode/FileInterface/RifVtkSurfaceImporter.cpp @@ -22,6 +22,7 @@ #include "Surface/RigTriangleMeshData.h" +#include #include #include #include @@ -61,6 +62,9 @@ std::unique_ptr RifVtkSurfaceImporter::importFromXmlDoc( co std::vector connectivity = RifVtkImportUtil::readConnectivity( piece ); if ( connectivity.empty() ) return nullptr; + // Connectivity values are indices into the vertex array and must be valid + if ( std::ranges::any_of( connectivity, [&vertices]( unsigned index ) { return index >= vertices.size(); } ) ) return nullptr; + // Avoid shared nodes std::vector nonSharedVertices; std::vector nonSharedConnectivity; diff --git a/ApplicationLibCode/GeoMech/GeoMechFileInterface/RifVtkReader.cpp b/ApplicationLibCode/GeoMech/GeoMechFileInterface/RifVtkReader.cpp index 4210fa29d0..c11f188591 100644 --- a/ApplicationLibCode/GeoMech/GeoMechFileInterface/RifVtkReader.cpp +++ b/ApplicationLibCode/GeoMech/GeoMechFileInterface/RifVtkReader.cpp @@ -33,6 +33,7 @@ #include +#include #include namespace RifVtkFieldNames @@ -292,6 +293,11 @@ std::expected std::vector connectivity = RifVtkImportUtil::readConnectivity( piece ); if ( connectivity.empty() ) return std::unexpected( "No connectivity found: " + d.filepath.string() ); + if ( std::ranges::any_of( connectivity, [&vertices]( unsigned index ) { return index >= vertices.size(); } ) ) + { + return std::unexpected( "Connectivity refers to a point that does not exist: " + d.filepath.string() ); + } + std::vector> partNodes; for ( int i = 0; i < static_cast( vertices.size() ); i++ ) { diff --git a/ApplicationLibCode/UnitTests/RifActiveCellsReader-Test.cpp b/ApplicationLibCode/UnitTests/RifActiveCellsReader-Test.cpp index ae704947a0..3497b58b17 100644 --- a/ApplicationLibCode/UnitTests/RifActiveCellsReader-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifActiveCellsReader-Test.cpp @@ -22,10 +22,12 @@ #include "RiaTestDataDirectory.h" #include "RifActiveCellsReader.h" +#include "ert/ecl/ecl_endian_flip.hpp" #include "ert/ecl/ecl_file.hpp" #include "ert/ecl/ecl_grid.hpp" #include +#include //-------------------------------------------------------------------------------------------------- /// @@ -118,3 +120,44 @@ TEST( RifActiveCellsReaderTest, BasicTest10k ) ecl_grid_free( mainEclGrid ); } + +//-------------------------------------------------------------------------------------------------- +/// Odd number of PORV values for a dual porosity case. Used to write one int past the end of the +/// active cell vector. +//-------------------------------------------------------------------------------------------------- +TEST( RifActiveCellsReaderTest, DualPorosityWithOddPorvCount ) +{ + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ); + + const int porvCount = 11; + const QString filePath = tempDir.path() + "/ODD_PORV.INIT"; + + { + ecl_kw_type* kw = ecl_kw_alloc( "PORV", porvCount, ecl_type_create_from_type( ECL_FLOAT_TYPE ) ); + float* data = static_cast( ecl_kw_get_ptr( kw ) ); + for ( int i = 0; i < porvCount; i++ ) + { + data[i] = 1.0f + static_cast( i ); + } + + fortio_type* fortio = fortio_open_writer( RiaStringEncodingTools::toNativeEncoded( filePath ).data(), false, ECL_ENDIAN_FLIP ); + ASSERT_NE( nullptr, fortio ); + ecl_kw_fwrite( kw, fortio ); + fortio_fclose( fortio ); + ecl_kw_free( kw ); + } + + ecl_file_type* initFile = ecl_file_open( RiaStringEncodingTools::toNativeEncoded( filePath ).data(), ECL_FILE_CLOSE_STREAM ); + ASSERT_NE( nullptr, initFile ); + + auto activeCells = RifActiveCellsReader::activeCellsFromPorvKeyword( initFile, true, 0 ); + ecl_file_close( initFile ); + + ASSERT_EQ( size_t( 1 ), activeCells.size() ); + ASSERT_EQ( size_t( porvCount / 2 ), activeCells[0].size() ); + for ( int value : activeCells[0] ) + { + EXPECT_GT( value, 0 ); + } +} diff --git a/ApplicationLibCode/UnitTests/RifEclipseInputFileTools-Test.cpp b/ApplicationLibCode/UnitTests/RifEclipseInputFileTools-Test.cpp index 43e63848c9..0a80f763b5 100644 --- a/ApplicationLibCode/UnitTests/RifEclipseInputFileTools-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifEclipseInputFileTools-Test.cpp @@ -843,6 +843,55 @@ TEST( RifEclipseInputFileToolsTest, ExportGridInvalidParams ) EXPECT_FALSE( exportSuccess ) << "Should fail when passing nullptr eclipseCase"; } +//-------------------------------------------------------------------------------------------------- +/// ZCORN size does not match SPECGRID. Used to dereference a null grid and crash. +//-------------------------------------------------------------------------------------------------- +TEST( RifEclipseInputFileToolsTest, OpenGridFileWithMismatchingZcornSize ) +{ + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ); + + QString fileName = tempDir.path() + "/mismatch.grdecl"; + { + QFile file( fileName ); + ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) ); + QTextStream out( &file ); + out << "SPECGRID\n 10 10 10 1 F\n/\n\nCOORD\n"; + for ( int i = 0; i < 726; i++ ) + out << "1.0 "; + out << "\n/\n\nZCORN\n 1.0 1.0 1.0 1.0\n/\n"; + } + + auto eclipseCase = std::make_unique( nullptr ); + QString errorMessages; + bool success = RifEclipseInputFileTools::openGridFile( fileName, eclipseCase.get(), false, &errorMessages ); + + EXPECT_FALSE( success ); +} + +//-------------------------------------------------------------------------------------------------- +/// SPECGRID with too few values. Used to read outside the keyword data. +//-------------------------------------------------------------------------------------------------- +TEST( RifEclipseInputFileToolsTest, OpenGridFileWithShortSpecgrid ) +{ + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ); + + QString fileName = tempDir.path() + "/short_specgrid.grdecl"; + { + QFile file( fileName ); + ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) ); + QTextStream out( &file ); + out << "SPECGRID\n 1\n/\n\nCOORD\n 0 0 0 0 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1 0 1 1 1\n/\n\nZCORN\n 0 0 0 0 1 1 1 1\n/\n"; + } + + auto eclipseCase = std::make_unique( nullptr ); + QString errorMessages; + bool success = RifEclipseInputFileTools::openGridFile( fileName, eclipseCase.get(), false, &errorMessages ); + + EXPECT_FALSE( success ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/UnitTests/RifPerforationIntervalReader-Test.cpp b/ApplicationLibCode/UnitTests/RifPerforationIntervalReader-Test.cpp index f2b890accb..8ad452485c 100644 --- a/ApplicationLibCode/UnitTests/RifPerforationIntervalReader-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifPerforationIntervalReader-Test.cpp @@ -7,6 +7,10 @@ #include // Needed for HUGE_VAL on Linux #include +#include +#include +#include + static const QString PERFORATION_TEST_DATA_DIRECTORY = QString( "%1/RifPerforationIntervalReader/" ).arg( TEST_DATA_DIR ); //-------------------------------------------------------------------------------------------------- @@ -19,3 +23,30 @@ TEST( RifPerforationIntervalReaderTest, SpacesInWellNameHandledSuccessfully ) EXPECT_EQ( size_t( 10 ), perforationIntervals["A1_RI_HZX"].size() ); } + +//-------------------------------------------------------------------------------------------------- +/// Records with too few fields used to read outside the list of parts and crash. +//-------------------------------------------------------------------------------------------------- +TEST( RifPerforationIntervalReaderTest, ShortRecordsAreSkipped ) +{ + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ); + + QString fileName = tempDir.path() + "/short.ev"; + { + QFile file( fileName ); + ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) ); + QTextStream out( &file ); + out << "WELLNAME A1\n"; + out << "10 JAN 2025 perforation 1500 2000\n"; + out << "10 JAN 2025 perforation 1500 2000 0.2 0.0\n"; + out << "START perforation 1500 2000\n"; + out << "START perforation 1500 2000 0.2 0.0\n"; + } + + auto intervals = RifPerforationIntervalReader::readPerforationIntervals( fileName ); + + ASSERT_EQ( size_t( 2 ), intervals["A1"].size() ); + EXPECT_FALSE( intervals["A1"][0].startOfHistory ); + EXPECT_TRUE( intervals["A1"][1].startOfHistory ); +} diff --git a/ApplicationLibCode/UnitTests/RifVtkSurfaceImporter-Test.cpp b/ApplicationLibCode/UnitTests/RifVtkSurfaceImporter-Test.cpp index ffac1f271d..466fc3049e 100644 --- a/ApplicationLibCode/UnitTests/RifVtkSurfaceImporter-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifVtkSurfaceImporter-Test.cpp @@ -8,9 +8,12 @@ #include #include +#include #include #include +#include + // Test importing a VTU file TEST( RifVtkSurfaceImporterTest, ImportFromFile ) { @@ -52,6 +55,45 @@ TEST( RifVtkSurfaceImporterTest, ImportFromFile ) } } +// Connectivity referring to a non-existing vertex used to read outside the vertex array +TEST( RifVtkSurfaceImporterTest, ConnectivityOutOfRangeIsRejected ) +{ + const std::string xml = R"( + + + + + 0 0 0 1 0 0 0 1 0 + + + 100000000 1 2 + 3 + 5 + + + +)"; + + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ); + const auto filePath = std::filesystem::path( tempDir.path().toStdString() ) / "surface.vtu"; + + auto writeFile = [&filePath]( const std::string& content ) + { + std::ofstream out( filePath ); + out << content; + }; + + writeFile( xml ); + EXPECT_EQ( nullptr, RifVtkSurfaceImporter::importFromFile( filePath ) ); + + // The same file with valid indices is accepted + auto validXml = xml; + validXml.replace( validXml.find( "100000000" ), 9, "0" ); + writeFile( validXml ); + EXPECT_NE( nullptr, RifVtkSurfaceImporter::importFromFile( filePath ) ); +} + // Test parsing a PVD file TEST( RifVtkSurfaceImporterTest, ParsePvdDatasets ) { diff --git a/ThirdParty/roffcpp b/ThirdParty/roffcpp index 36c20f89a3..4e8e1def00 160000 --- a/ThirdParty/roffcpp +++ b/ThirdParty/roffcpp @@ -1 +1 @@ -Subproject commit 36c20f89a3933e14506aa3758cf69da425459ba8 +Subproject commit 4e8e1def00033e5fa49e1a60be4d143b2a8870fb