LCOV - code coverage report
Current view: top level - src/odbc/unittests - offset_ptr.c (source / functions) Hit Total Coverage
Test: FreeTDS coverage Lines: 65 69 94.2 %
Date: 2026-03-24 22:22:09 Functions: 4 4 100.0 %

          Line data    Source code
       1             : #include "common.h"
       2             : 
       3             : /*
       4             :  * Row layout for row-wise binding: one value and one length/indicator
       5             :  * per column, matching the buffer layout pyodbc uses in ExecuteMulti().
       6             :  */
       7             : typedef struct
       8             : {
       9             :         SQLINTEGER id1;
      10             :         SQLLEN ind1;
      11             :         char ch1[8];
      12             :         SQLLEN ind2;
      13             : } Row;
      14             : 
      15             : /*
      16             :  * Minimal reproducible example for a segfault in FreeTDS when
      17             :  * SQL_ATTR_PARAM_BIND_OFFSET_PTR was set on a statement handle.
      18             :  *
      19             :  * Test from Bob Kline
      20             :  */
      21             : static void
      22          10 : test_insert(void)
      23             : {
      24             :         const char *sql;
      25             :         char *base;
      26             : 
      27             :         /* Three rows of actual parameter data. */
      28             :         static const Row rows[3] = {
      29             :                 {10, 0, "hixxx", 2},
      30             :                 {20, 0, "hello", 5},
      31             :                 {30, 0, "bye", SQL_NULL_DATA},
      32             :         };
      33             : 
      34             :         /*
      35             :          * bop: the bind offset.
      36             :          * base(0x10) + bop == &rows[0], so a conformant driver will read
      37             :          * id1=10, ch1='hi' for the first row, id1=20, ch1='hello' for the second
      38             :          * and id1=30, ch1=NULL for the third.
      39             :          */
      40          10 :         SQLULEN bop = (SQLULEN) (TDS_INTPTR) rows - 16;
      41             : 
      42          10 :         CHKPrepare(T("INSERT INTO #offset_ptr(id1, ch1) VALUES (?, ?)"), SQL_NTS, "S");
      43             : 
      44             :         /*
      45             :          * Bind parameters using a non-null but deliberately invalid base
      46             :          * pointer (decimal 16 = 0x10). The real data buffer will be supplied
      47             :          * via SQL_ATTR_PARAM_BIND_OFFSET_PTR below. The ODBC spec explicitly
      48             :          * permits this: "either or both the offset and the address to which
      49             :          * the offset is added can be invalid, as long as their sum is a valid
      50             :          * address."
      51             :          */
      52          10 :         base = (char *) (TDS_INTPTR) 16;
      53          10 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 10, 0, (SQLPOINTER) base, sizeof(SQLINTEGER),
      54             :                          (SQLLEN *) (base + TDS_OFFSET(Row, ind1)), "S");
      55          10 :         CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 10, 0, (SQLPOINTER) (base + TDS_OFFSET(Row, ch1)),
      56             :                          sizeof(rows[0].ch1), (SQLLEN *) (base + TDS_OFFSET(Row, ind2)), "S");
      57             : 
      58          10 :         CHKSetStmtAttr(SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER) sizeof(Row), SQL_IS_UINTEGER, "S");
      59          10 :         CHKSetStmtAttr(SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) 3, SQL_IS_UINTEGER, "S");
      60          10 :         CHKSetStmtAttr(SQL_ATTR_PARAM_BIND_OFFSET_PTR, (SQLPOINTER) & bop, SQL_IS_POINTER, "S");
      61             : 
      62             :         /* FreeTDS crashed here with SIGSEGV in tds_convert() */
      63          10 :         printf("Calling SQLExecute...\n");
      64          10 :         CHKExecute("SI");
      65             : 
      66          10 :         odbc_reset_statement();
      67             : 
      68          10 :         sql = "IF NOT EXISTS(SELECT * FROM #offset_ptr WHERE id1 = 10 AND ch1 = 'hi') "
      69             :                 "OR NOT EXISTS(SELECT * FROM #offset_ptr WHERE id1 = 20 AND ch1 = 'hello') "
      70             :                 "OR NOT EXISTS(SELECT * FROM #offset_ptr WHERE id1 = 30 AND ch1 IS NULL) SELECT 1";
      71          10 :         odbc_check_no_row(sql);
      72             : 
      73          10 :         printf("PASS: three rows inserted.\n");
      74          10 : }
      75             : 
      76             : static void
      77          90 : check_row(const Row *row, int idx, int id, const char *s)
      78             : {
      79             :         bool s_ok;
      80             : 
      81          90 :         if (s)
      82          60 :                 s_ok = (strcmp(row->ch1, s) == 0) && (row->ind2 == strlen(s));
      83             :         else
      84          30 :                 s_ok = (row->ind2 == SQL_NULL_DATA);
      85             : 
      86          90 :         if (row->id1 != id || !s_ok) {
      87             :                 char ch1[sizeof(row->ch1) + 1];
      88             : 
      89           0 :                 memcpy(ch1, row->ch1, sizeof(row->ch1));
      90           0 :                 ch1[sizeof(row->ch1)] = 0;
      91           0 :                 fprintf(stderr, "Wrong row %d: id %d ch1 %s ind2 %d\n", idx, (int) row->id1, ch1, (int) row->ind2);
      92           0 :                 exit(1);
      93             :         }
      94          90 : }
      95             : 
      96             : typedef enum
      97             : {
      98             :         FETCH_NORMAL,
      99             :         FETCH_SCROLL,
     100             :         FETCH_EXTENDED,
     101             : } FetchType;
     102             : 
     103             : static void
     104          30 : test_fetch(FetchType fetch_type)
     105             : {
     106             :         char *base;
     107             :         Row rows[3];
     108          30 :         SQLULEN bop = (SQLULEN) (TDS_INTPTR) rows - 16;
     109             : 
     110             : #ifdef HAVE_SQLROWSETSIZE
     111             :         SQLROWSETSIZE set_size = 0;
     112             : #else
     113          30 :         SQLULEN set_size = 0;
     114             : #endif
     115             : 
     116          30 :         memset(rows, '*', sizeof(rows));
     117             : 
     118          30 :         CHKPrepare(T("SELECT * FROM #offset_ptr ORDER BY id1"), SQL_NTS, "S");
     119             : 
     120          30 :         base = (char *) (TDS_INTPTR) 16;
     121          30 :         CHKBindCol(1, SQL_C_SLONG, (SQLPOINTER) base, sizeof(SQLINTEGER), (SQLLEN *) (base + TDS_OFFSET(Row, ind1)), "S");
     122          30 :         CHKBindCol(2, SQL_C_CHAR, (SQLPOINTER) (base + TDS_OFFSET(Row, ch1)), sizeof(rows[0].ch1),
     123             :                    (SQLLEN *) (base + TDS_OFFSET(Row, ind2)), "S");
     124             : 
     125          30 :         CHKSetStmtAttr(SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER) sizeof(Row), SQL_IS_UINTEGER, "S");
     126          30 :         if (fetch_type != FETCH_EXTENDED)
     127          20 :                 CHKSetStmtAttr(SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 3, SQL_IS_UINTEGER, "S");
     128             :         else
     129          10 :                 CHKSetStmtAttr(SQL_ROWSET_SIZE, (SQLPOINTER) TDS_INT2PTR(3), 0, "S");
     130          30 :         CHKSetStmtAttr(SQL_ATTR_ROW_BIND_OFFSET_PTR, (SQLPOINTER) & bop, SQL_IS_POINTER, "S");
     131             : 
     132             :         /* FreeTDS crashed here with SIGSEGV in tds_convert() */
     133          30 :         printf("Calling SQLExecute...\n");
     134          30 :         CHKExecute("SI");
     135          30 :         switch (fetch_type) {
     136          10 :         case FETCH_NORMAL:
     137          10 :                 CHKFetch("S");
     138          10 :                 break;
     139          10 :         case FETCH_SCROLL:
     140          10 :                 CHKFetchScroll(SQL_FETCH_NEXT, 0, "S");
     141          10 :                 break;
     142          10 :         case FETCH_EXTENDED:
     143          10 :                 CHKExtendedFetch(SQL_FETCH_NEXT, 0, &set_size, NULL, "S");
     144          10 :                 break;
     145             :         }
     146          60 :         while (CHKMoreResults("SNo") == SQL_SUCCESS)
     147          30 :                 continue;
     148             : 
     149          30 :         odbc_reset_statement();
     150             : 
     151          30 :         check_row(&rows[0], 0, 10, "hi");
     152          30 :         check_row(&rows[1], 1, 20, "hello");
     153          30 :         check_row(&rows[2], 2, 30, NULL);
     154             : 
     155          30 :         printf("PASS: three rows returned.\n");
     156          30 : }
     157             : 
     158          10 : TEST_MAIN()
     159             : {
     160          10 :         odbc_use_version3 = true;
     161          10 :         odbc_connect();
     162             : 
     163          10 :         odbc_command("CREATE TABLE #offset_ptr (id1 INT, ch1 VARCHAR(10) NULL)");
     164             : 
     165          10 :         test_insert();
     166             : 
     167          10 :         test_fetch(FETCH_NORMAL);
     168          10 :         test_fetch(FETCH_SCROLL);
     169          10 :         test_fetch(FETCH_EXTENDED);
     170             : 
     171          10 :         odbc_disconnect();
     172          10 :         return 0;
     173             : }

Generated by: LCOV version 1.13