LCOV - code coverage report
Current view: top level - frmts/r - rdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 174 254 68.5 %
Date: 2025-02-20 10:14:44 Functions: 14 14 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  R Format Driver
       4             :  * Purpose:  Read/write R stats package object format.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
       9             :  * Copyright (c) 2009-2010, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "rdataset.h"
      16             : 
      17             : #include <cstddef>
      18             : #include <cstdlib>
      19             : #include <cstring>
      20             : #if HAVE_FCNTL_H
      21             : #include <fcntl.h>
      22             : #endif
      23             : 
      24             : #include <algorithm>
      25             : #include <limits>
      26             : #include <string>
      27             : #include <utility>
      28             : 
      29             : #include "cpl_conv.h"
      30             : #include "cpl_error.h"
      31             : #include "cpl_progress.h"
      32             : #include "cpl_string.h"
      33             : #include "cpl_vsi.h"
      34             : #include "gdal.h"
      35             : #include "gdal_frmts.h"
      36             : #include "gdal_pam.h"
      37             : #include "gdal_priv.h"
      38             : 
      39             : // constexpr int R_NILSXP = 0;
      40             : constexpr int R_LISTSXP = 2;
      41             : constexpr int R_CHARSXP = 9;
      42             : constexpr int R_INTSXP = 13;
      43             : constexpr int R_REALSXP = 14;
      44             : constexpr int R_STRSXP = 16;
      45             : 
      46             : namespace
      47             : {
      48             : 
      49             : // TODO(schwehr): Move this to port/? for general use.
      50          20 : bool SafeMult(GIntBig a, GIntBig b, GIntBig *result)
      51             : {
      52          20 :     if (a == 0 || b == 0)
      53             :     {
      54           0 :         *result = 0;
      55           0 :         return true;
      56             :     }
      57             : 
      58          20 :     bool result_positive = (a >= 0 && b >= 0) || (a < 0 && b < 0);
      59          20 :     if (result_positive)
      60             :     {
      61             :         // Cannot convert min() to positive.
      62          40 :         if (a == std::numeric_limits<GIntBig>::min() ||
      63          20 :             b == std::numeric_limits<GIntBig>::min())
      64             :         {
      65           0 :             *result = 0;
      66           0 :             return false;
      67             :         }
      68          20 :         if (a < 0)
      69             :         {
      70           0 :             a = -a;
      71           0 :             b = -b;
      72             :         }
      73          20 :         if (a > std::numeric_limits<GIntBig>::max() / b)
      74             :         {
      75           0 :             *result = 0;
      76           0 :             return false;
      77             :         }
      78          20 :         *result = a * b;
      79          20 :         return true;
      80             :     }
      81             : 
      82           0 :     if (b < a)
      83           0 :         std::swap(a, b);
      84           0 :     if (a < (std::numeric_limits<GIntBig>::min() + 1) / b)
      85             :     {
      86           0 :         *result = 0;
      87           0 :         return false;
      88             :     }
      89             : 
      90           0 :     *result = a * b;
      91           0 :     return true;
      92             : }
      93             : 
      94             : }  // namespace
      95             : 
      96             : /************************************************************************/
      97             : /*                            RRasterBand()                             */
      98             : /************************************************************************/
      99             : 
     100           7 : RRasterBand::RRasterBand(RDataset *poDSIn, int nBandIn,
     101           7 :                          const double *padfMatrixValuesIn)
     102           7 :     : padfMatrixValues(padfMatrixValuesIn)
     103             : {
     104           7 :     poDS = poDSIn;
     105           7 :     nBand = nBandIn;
     106             : 
     107           7 :     eDataType = GDT_Float64;
     108             : 
     109           7 :     nBlockXSize = poDSIn->nRasterXSize;
     110           7 :     nBlockYSize = 1;
     111           7 : }
     112             : 
     113             : /************************************************************************/
     114             : /*                             IReadBlock()                             */
     115             : /************************************************************************/
     116             : 
     117          45 : CPLErr RRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
     118             :                                void *pImage)
     119             : {
     120          45 :     memcpy(pImage, padfMatrixValues + nBlockYOff * nBlockXSize,
     121          45 :            nBlockXSize * 8);
     122          45 :     return CE_None;
     123             : }
     124             : 
     125             : /************************************************************************/
     126             : /* ==================================================================== */
     127             : /*                              RDataset()                              */
     128             : /* ==================================================================== */
     129             : /************************************************************************/
     130             : 
     131             : /************************************************************************/
     132             : /*                              RDataset()                              */
     133             : /************************************************************************/
     134             : 
     135          10 : RDataset::RDataset()
     136          10 :     : fp(nullptr), bASCII(FALSE), nStartOfData(0), padfMatrixValues(nullptr)
     137             : {
     138          10 : }
     139             : 
     140             : /************************************************************************/
     141             : /*                             ~RDataset()                              */
     142             : /************************************************************************/
     143             : 
     144          20 : RDataset::~RDataset()
     145             : {
     146          10 :     FlushCache(true);
     147          10 :     CPLFree(padfMatrixValues);
     148             : 
     149          10 :     if (fp)
     150          10 :         VSIFCloseL(fp);
     151          20 : }
     152             : 
     153             : /************************************************************************/
     154             : /*                             ASCIIFGets()                             */
     155             : /*                                                                      */
     156             : /*      Fetch one line from an ASCII source into osLastStringRead.      */
     157             : /************************************************************************/
     158             : 
     159        1385 : const char *RDataset::ASCIIFGets()
     160             : 
     161             : {
     162        1385 :     char chNextChar = '\0';
     163             : 
     164        1385 :     osLastStringRead.resize(0);
     165             : 
     166        3952 :     do
     167             :     {
     168        5337 :         chNextChar = '\n';
     169        5337 :         VSIFReadL(&chNextChar, 1, 1, fp);
     170        5337 :         if (chNextChar != '\n')
     171        3952 :             osLastStringRead += chNextChar;
     172        5337 :     } while (chNextChar != '\n' && chNextChar != '\0');
     173             : 
     174        2770 :     return osLastStringRead;
     175             : }
     176             : 
     177             : /************************************************************************/
     178             : /*                            ReadInteger()                             */
     179             : /************************************************************************/
     180             : 
     181         190 : int RDataset::ReadInteger()
     182             : 
     183             : {
     184         190 :     if (bASCII)
     185             :     {
     186          95 :         return atoi(ASCIIFGets());
     187             :     }
     188             : 
     189          95 :     GInt32 nValue = 0;
     190             : 
     191          95 :     if (VSIFReadL(&nValue, 4, 1, fp) != 1)
     192           0 :         return -1;
     193          95 :     CPL_MSBPTR32(&nValue);
     194             : 
     195          95 :     return nValue;
     196             : }
     197             : 
     198             : /************************************************************************/
     199             : /*                             ReadFloat()                              */
     200             : /************************************************************************/
     201             : 
     202        1280 : double RDataset::ReadFloat()
     203             : 
     204             : {
     205        1280 :     if (bASCII)
     206             :     {
     207        1280 :         return CPLAtof(ASCIIFGets());
     208             :     }
     209             : 
     210           0 :     double dfValue = 0.0;
     211             : 
     212           0 :     if (VSIFReadL(&dfValue, 8, 1, fp) != 1)
     213           0 :         return -1;
     214           0 :     CPL_MSBPTR64(&dfValue);
     215             : 
     216           0 :     return dfValue;
     217             : }
     218             : 
     219             : /************************************************************************/
     220             : /*                             ReadString()                             */
     221             : /************************************************************************/
     222             : 
     223          20 : const char *RDataset::ReadString()
     224             : 
     225             : {
     226          20 :     if (ReadInteger() % 256 != R_CHARSXP)
     227             :     {
     228           0 :         osLastStringRead = "";
     229           0 :         return "";
     230             :     }
     231             : 
     232          20 :     const int nLenSigned = ReadInteger();
     233          20 :     if (nLenSigned < 0)
     234             :     {
     235           0 :         osLastStringRead = "";
     236           0 :         return "";
     237             :     }
     238          20 :     const size_t nLen = static_cast<size_t>(nLenSigned);
     239             : 
     240          20 :     char *pachWrkBuf = static_cast<char *>(VSIMalloc(nLen));
     241          20 :     if (pachWrkBuf == nullptr)
     242             :     {
     243           0 :         osLastStringRead = "";
     244           0 :         return "";
     245             :     }
     246          20 :     if (VSIFReadL(pachWrkBuf, 1, nLen, fp) != nLen)
     247             :     {
     248           0 :         osLastStringRead = "";
     249           0 :         CPLFree(pachWrkBuf);
     250           0 :         return "";
     251             :     }
     252             : 
     253          20 :     if (bASCII)
     254             :     {
     255             :         // Suck up newline and any extra junk.
     256          10 :         ASCIIFGets();
     257             :     }
     258             : 
     259          20 :     osLastStringRead.assign(pachWrkBuf, nLen);
     260          20 :     CPLFree(pachWrkBuf);
     261             : 
     262          20 :     return osLastStringRead;
     263             : }
     264             : 
     265             : /************************************************************************/
     266             : /*                              ReadPair()                              */
     267             : /************************************************************************/
     268             : 
     269          30 : bool RDataset::ReadPair(CPLString &osObjName, int &nObjCode)
     270             : 
     271             : {
     272          30 :     nObjCode = ReadInteger();
     273          30 :     if (nObjCode == 254)
     274          10 :         return true;
     275             : 
     276          20 :     if ((nObjCode % 256) != R_LISTSXP)
     277             :     {
     278           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     279             :                  "Did not find expected object pair object.");
     280           0 :         return false;
     281             :     }
     282             : 
     283          20 :     int nPairCount = ReadInteger();
     284          20 :     if (nPairCount != 1)
     285             :     {
     286           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     287             :                  "Did not find expected pair count of 1.");
     288           0 :         return false;
     289             :     }
     290             : 
     291             :     // Read the object name.
     292          20 :     const char *pszName = ReadString();
     293          20 :     if (pszName == nullptr || pszName[0] == '\0')
     294           0 :         return false;
     295             : 
     296          20 :     osObjName = pszName;
     297             : 
     298             :     // Confirm that we have a numeric matrix object.
     299          20 :     nObjCode = ReadInteger();
     300             : 
     301          20 :     return true;
     302             : }
     303             : 
     304             : /************************************************************************/
     305             : /*                              Identify()                              */
     306             : /************************************************************************/
     307             : 
     308       53821 : int RDataset::Identify(GDALOpenInfo *poOpenInfo)
     309             : {
     310       53821 :     if (poOpenInfo->nHeaderBytes < 50)
     311       49502 :         return FALSE;
     312             : 
     313             :     // If the extension is .rda and the file type is gzip
     314             :     // compressed we assume it is a gzipped R binary file.
     315        4353 :     if (memcmp(poOpenInfo->pabyHeader, "\037\213\b", 3) == 0 &&
     316          34 :         poOpenInfo->IsExtensionEqualToCI("rda"))
     317           6 :         return TRUE;
     318             : 
     319             :     // Is this an ASCII or XDR binary R file?
     320        4313 :     if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "RDA2\nA\n") &&
     321        4303 :         !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "RDX2\nX\n"))
     322        4299 :         return FALSE;
     323             : 
     324          14 :     return TRUE;
     325             : }
     326             : 
     327             : /************************************************************************/
     328             : /*                                Open()                                */
     329             : /************************************************************************/
     330             : 
     331          10 : GDALDataset *RDataset::Open(GDALOpenInfo *poOpenInfo)
     332             : {
     333             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     334             :     if (poOpenInfo->pabyHeader == nullptr)
     335             :         return nullptr;
     336             : #else
     337             :     // During fuzzing, do not use Identify to reject crazy content.
     338          10 :     if (!Identify(poOpenInfo))
     339           0 :         return nullptr;
     340             : #endif
     341             : 
     342             :     // Confirm the requested access is supported.
     343          10 :     if (poOpenInfo->eAccess == GA_Update)
     344             :     {
     345           0 :         ReportUpdateNotSupportedByDriver("R");
     346           0 :         return nullptr;
     347             :     }
     348             : 
     349             :     // Do we need to route the file through the decompression machinery?
     350          10 :     const bool bCompressed =
     351          10 :         memcmp(poOpenInfo->pabyHeader, "\037\213\b", 3) == 0;
     352             :     const CPLString osAdjustedFilename =
     353          30 :         std::string(bCompressed ? "/vsigzip/" : "") + poOpenInfo->pszFilename;
     354             : 
     355             :     // Establish this as a dataset and open the file using VSI*L.
     356          20 :     auto poDS = std::make_unique<RDataset>();
     357             : 
     358          10 :     poDS->fp = VSIFOpenL(osAdjustedFilename, "r");
     359          10 :     if (poDS->fp == nullptr)
     360             :     {
     361           0 :         return nullptr;
     362             :     }
     363             : 
     364          10 :     poDS->bASCII = STARTS_WITH_CI(
     365             :         reinterpret_cast<char *>(poOpenInfo->pabyHeader), "RDA2\nA\n");
     366             : 
     367             :     // Confirm this is a version 2 file.
     368          10 :     VSIFSeekL(poDS->fp, 7, SEEK_SET);
     369          10 :     if (poDS->ReadInteger() != R_LISTSXP)
     370             :     {
     371           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     372             :                  "It appears %s is not a version 2 R object file after all!",
     373             :                  poOpenInfo->pszFilename);
     374           0 :         return nullptr;
     375             :     }
     376             : 
     377             :     // Skip the version values.
     378          10 :     poDS->ReadInteger();
     379          10 :     poDS->ReadInteger();
     380             : 
     381             :     // Confirm we have a numeric vector object in a pairlist.
     382          20 :     CPLString osObjName;
     383          10 :     int nObjCode = 0;
     384             : 
     385          10 :     if (!poDS->ReadPair(osObjName, nObjCode))
     386             :     {
     387           0 :         return nullptr;
     388             :     }
     389             : 
     390          10 :     if (nObjCode % 256 != R_REALSXP)
     391             :     {
     392           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     393             :                  "Failed to find expected numeric vector object.");
     394           0 :         return nullptr;
     395             :     }
     396             : 
     397          10 :     poDS->SetMetadataItem("R_OBJECT_NAME", osObjName);
     398             : 
     399             :     // Read the count.
     400          10 :     const int nValueCount = poDS->ReadInteger();
     401          10 :     if (nValueCount < 0)
     402             :     {
     403           0 :         CPLError(CE_Failure, CPLE_AppDefined, "nValueCount < 0: %d",
     404             :                  nValueCount);
     405           0 :         return nullptr;
     406             :     }
     407             : 
     408          10 :     poDS->nStartOfData = VSIFTellL(poDS->fp);
     409             : 
     410             :     // TODO(schwehr): Factor in the size of doubles.
     411             :     VSIStatBufL stat;
     412             :     const int dStatSuccess =
     413          10 :         VSIStatExL(osAdjustedFilename, &stat, VSI_STAT_SIZE_FLAG);
     414          20 :     if (dStatSuccess != 0 || static_cast<vsi_l_offset>(nValueCount) >
     415          10 :                                  stat.st_size - poDS->nStartOfData)
     416             :     {
     417           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     418             :                  "Corrupt file.  "
     419             :                  "Object claims to be larger than available bytes. "
     420             :                  "%d > " CPL_FRMT_GUIB,
     421           0 :                  nValueCount, stat.st_size - poDS->nStartOfData);
     422           0 :         return nullptr;
     423             :     }
     424             : 
     425             :     // Read/Skip ahead to attributes.
     426          10 :     if (poDS->bASCII)
     427             :     {
     428          10 :         poDS->padfMatrixValues =
     429           5 :             static_cast<double *>(VSIMalloc2(nValueCount, sizeof(double)));
     430           5 :         if (poDS->padfMatrixValues == nullptr)
     431             :         {
     432           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot allocate %d doubles",
     433             :                      nValueCount);
     434           0 :             return nullptr;
     435             :         }
     436        1285 :         for (int iValue = 0; iValue < nValueCount; iValue++)
     437        1280 :             poDS->padfMatrixValues[iValue] = poDS->ReadFloat();
     438             :     }
     439             :     else
     440             :     {
     441           5 :         VSIFSeekL(poDS->fp, 8 * nValueCount, SEEK_CUR);
     442             :     }
     443             : 
     444             :     // Read pairs till we run out, trying to find a few items that
     445             :     // have special meaning to us.
     446          10 :     poDS->nRasterXSize = 0;
     447          10 :     poDS->nRasterYSize = 0;
     448          10 :     int nBandCount = 0;
     449             : 
     450          20 :     while (poDS->ReadPair(osObjName, nObjCode) && nObjCode != 254)
     451             :     {
     452          10 :         if (osObjName == "dim" && nObjCode % 256 == R_INTSXP)
     453             :         {
     454          10 :             const int nCount = poDS->ReadInteger();
     455          10 :             if (nCount == 2)
     456             :             {
     457           0 :                 poDS->nRasterXSize = poDS->ReadInteger();
     458           0 :                 poDS->nRasterYSize = poDS->ReadInteger();
     459           0 :                 nBandCount = 1;
     460             :             }
     461          10 :             else if (nCount == 3)
     462             :             {
     463          10 :                 poDS->nRasterXSize = poDS->ReadInteger();
     464          10 :                 poDS->nRasterYSize = poDS->ReadInteger();
     465          10 :                 nBandCount = poDS->ReadInteger();
     466             :             }
     467             :             else
     468             :             {
     469           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     470             :                          "R 'dim' dimension wrong.");
     471           0 :                 return nullptr;
     472             :             }
     473             :         }
     474           0 :         else if (nObjCode % 256 == R_REALSXP)
     475             :         {
     476           0 :             int nCount = poDS->ReadInteger();
     477           0 :             while (nCount > 0 && !VSIFEofL(poDS->fp))
     478             :             {
     479           0 :                 nCount--;
     480           0 :                 poDS->ReadFloat();
     481             :             }
     482             :         }
     483           0 :         else if (nObjCode % 256 == R_INTSXP)
     484             :         {
     485           0 :             int nCount = poDS->ReadInteger();
     486           0 :             while (nCount > 0 && !VSIFEofL(poDS->fp))
     487             :             {
     488           0 :                 nCount--;
     489           0 :                 poDS->ReadInteger();
     490             :             }
     491             :         }
     492           0 :         else if (nObjCode % 256 == R_STRSXP)
     493             :         {
     494           0 :             int nCount = poDS->ReadInteger();
     495           0 :             while (nCount > 0 && !VSIFEofL(poDS->fp))
     496             :             {
     497           0 :                 nCount--;
     498           0 :                 poDS->ReadString();
     499             :             }
     500             :         }
     501           0 :         else if (nObjCode % 256 == R_CHARSXP)
     502             :         {
     503           0 :             poDS->ReadString();
     504             :         }
     505             :     }
     506             : 
     507          10 :     if (poDS->nRasterXSize == 0)
     508             :     {
     509           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     510             :                  "Failed to find dim dimension information for R dataset.");
     511           0 :         return nullptr;
     512             :     }
     513             : 
     514          20 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
     515          10 :         !GDALCheckBandCount(nBandCount, TRUE))
     516             :     {
     517           0 :         return nullptr;
     518             :     }
     519             : 
     520          10 :     GIntBig result = 0;
     521          10 :     bool ok = SafeMult(nBandCount, poDS->nRasterXSize, &result);
     522          10 :     ok &= SafeMult(result, poDS->nRasterYSize, &result);
     523             : 
     524          10 :     if (!ok || nValueCount < result)
     525             :     {
     526           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Not enough pixel data.");
     527           0 :         return nullptr;
     528             :     }
     529             : 
     530             :     // Create the raster band object(s).
     531          24 :     for (int iBand = 0; iBand < nBandCount; iBand++)
     532             :     {
     533           0 :         std::unique_ptr<GDALRasterBand> poBand;
     534             : 
     535          14 :         if (poDS->bASCII)
     536           7 :             poBand = std::make_unique<RRasterBand>(
     537           7 :                 poDS.get(), iBand + 1,
     538          14 :                 poDS->padfMatrixValues + static_cast<size_t>(iBand) *
     539           7 :                                              poDS->nRasterXSize *
     540          14 :                                              poDS->nRasterYSize);
     541             :         else
     542             :         {
     543          14 :             poBand = RawRasterBand::Create(
     544           7 :                 poDS.get(), iBand + 1, poDS->fp,
     545          14 :                 poDS->nStartOfData +
     546           7 :                     static_cast<vsi_l_offset>(poDS->nRasterXSize) *
     547           7 :                         poDS->nRasterYSize * 8 * iBand,
     548           7 :                 8, poDS->nRasterXSize * 8, GDT_Float64,
     549             :                 RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
     550           7 :                 RawRasterBand::OwnFP::NO);
     551           7 :             if (!poBand)
     552           0 :                 return nullptr;
     553             :         }
     554          14 :         poDS->SetBand(iBand + 1, std::move(poBand));
     555             :     }
     556             : 
     557             :     // Initialize any PAM information.
     558          10 :     poDS->SetDescription(poOpenInfo->pszFilename);
     559          10 :     poDS->TryLoadXML();
     560             : 
     561             :     // Check for overviews.
     562          10 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
     563             : 
     564          10 :     return poDS.release();
     565             : }
     566             : 
     567             : /************************************************************************/
     568             : /*                        GDALRegister_R()                              */
     569             : /************************************************************************/
     570             : 
     571        1686 : void GDALRegister_R()
     572             : 
     573             : {
     574        1686 :     if (GDALGetDriverByName("R") != nullptr)
     575         302 :         return;
     576             : 
     577        1384 :     GDALDriver *poDriver = new GDALDriver();
     578             : 
     579        1384 :     poDriver->SetDescription("R");
     580        1384 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     581        1384 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "R Object Data Store");
     582        1384 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/r.html");
     583        1384 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "rda");
     584        1384 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Float32");
     585        1384 :     poDriver->SetMetadataItem(
     586             :         GDAL_DMD_CREATIONOPTIONLIST,
     587             :         "<CreationOptionList>"
     588             :         "   <Option name='ASCII' type='boolean' description='For ASCII output, "
     589             :         "default NO'/>"
     590             :         "   <Option name='COMPRESS' type='boolean' description='Produced "
     591             :         "Compressed output, default YES'/>"
     592        1384 :         "</CreationOptionList>");
     593             : 
     594        1384 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     595             : 
     596        1384 :     poDriver->pfnOpen = RDataset::Open;
     597        1384 :     poDriver->pfnIdentify = RDataset::Identify;
     598        1384 :     poDriver->pfnCreateCopy = RCreateCopy;
     599             : 
     600        1384 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     601             : }

Generated by: LCOV version 1.14