This is an automated email from the ASF dual-hosted git repository.

jiangtian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new b85ac145a4c Fix C++ client time column access causing UB for non-long 
types (#17397) (#17400)
b85ac145a4c is described below

commit b85ac145a4c0e040ff81d51867ff7c5f616027a2
Author: ZIHAN DAI <[email protected]>
AuthorDate: Wed Apr 1 13:11:45 2026 +1100

    Fix C++ client time column access causing UB for non-long types (#17397) 
(#17400)
    
    Add tsBlockColumnIndex < 0 guards to all ByTsBlockColumnIndex getter
    methods that were missing them. When the time pseudo-column is accessed
    through a typed getter other than getLong or getString, throw
    IoTDBException instead of causing undefined behavior via getColumn(-1).
    
    Signed-off-by: Zihan Dai <[email protected]>
---
 .../client-cpp/src/main/IoTDBRpcDataSet.cpp        | 19 ++++++++++++
 iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp | 35 ++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp 
b/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp
index eba013598e4..2afef2b9968 100644
--- a/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp
+++ b/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp
@@ -271,8 +271,15 @@ boost::optional<bool> IoTDBRpcDataSet::getBoolean(const 
std::string& columnName)
     return getBooleanByTsBlockColumnIndex(index);
 }
 
+// Note: tsBlockColumnIndex < 0 indicates the time pseudo-column in tree model.
+// Only getLong and getString support reading the time column directly.
+// All other typed getters throw IoTDBException to prevent undefined behavior
+// from accessing valueColumns_ with a negative index.
 boost::optional<bool> IoTDBRpcDataSet::getBooleanByTsBlockColumnIndex(int32_t 
tsBlockColumnIndex) {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+        throw IoTDBException("Cannot read boolean from time column");
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) {
         lastReadWasNull_ = false;
         return 
curTsBlock_->getColumn(tsBlockColumnIndex)->getBoolean(tsBlockIndex_);
@@ -295,6 +302,9 @@ boost::optional<double> IoTDBRpcDataSet::getDouble(const 
std::string& columnName
 
 boost::optional<double> IoTDBRpcDataSet::getDoubleByTsBlockColumnIndex(int32_t 
tsBlockColumnIndex) {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+        throw IoTDBException("Cannot read double from time column");
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) {
         lastReadWasNull_ = false;
         return 
curTsBlock_->getColumn(tsBlockColumnIndex)->getDouble(tsBlockIndex_);
@@ -317,6 +327,9 @@ boost::optional<float> IoTDBRpcDataSet::getFloat(const 
std::string& columnName)
 
 boost::optional<float> IoTDBRpcDataSet::getFloatByTsBlockColumnIndex(int32_t 
tsBlockColumnIndex) {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+        throw IoTDBException("Cannot read float from time column");
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) {
         lastReadWasNull_ = false;
         return 
curTsBlock_->getColumn(tsBlockColumnIndex)->getFloat(tsBlockIndex_);
@@ -339,6 +352,9 @@ boost::optional<int32_t> IoTDBRpcDataSet::getInt(const 
std::string& columnName)
 
 boost::optional<int32_t> IoTDBRpcDataSet::getIntByTsBlockColumnIndex(int32_t 
tsBlockColumnIndex) {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+        throw IoTDBException("Cannot read int32 from time column");
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) {
         lastReadWasNull_ = false;
         TSDataType::TSDataType dataType = 
curTsBlock_->getColumn(tsBlockColumnIndex)->getDataType();
@@ -395,6 +411,9 @@ std::shared_ptr<Binary> IoTDBRpcDataSet::getBinary(const 
std::string& columnName
 
 std::shared_ptr<Binary> IoTDBRpcDataSet::getBinaryByTsBlockColumnIndex(int32_t 
tsBlockColumnIndex) {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+        throw IoTDBException("Cannot read binary from time column");
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) {
         lastReadWasNull_ = false;
         return 
curTsBlock_->getColumn(tsBlockColumnIndex)->getBinary(tsBlockIndex_);
diff --git a/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp 
b/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp
index d08e0c3fbb0..af7f13855a4 100644
--- a/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp
+++ b/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp
@@ -504,6 +504,41 @@ TEST_CASE("Test insertTablet multi datatype", 
"[testInsertTabletMultiDatatype]")
     REQUIRE(count == 100);
 }
 
+TEST_CASE("Test boolean column via DataIterator", 
"[testBooleanColumnDataIterator]") {
+    CaseReporter cr("testBooleanColumnDataIterator");
+    string deviceId = "root.test.d1";
+    string timeseries = "root.test.d1.s1";
+
+    if (session->checkTimeseriesExists(timeseries)) {
+        session->deleteTimeseries(timeseries);
+    }
+    session->createTimeseries(timeseries, TSDataType::BOOLEAN, 
TSEncoding::PLAIN, CompressionType::SNAPPY);
+
+    // Insert boolean values: even timestamps get true, odd get false
+    vector<string> measurements = {"s1"};
+    vector<TSDataType::TSDataType> types = {TSDataType::BOOLEAN};
+    for (int64_t time = 0; time < 10; time++) {
+        bool val = (time % 2 == 0);
+        vector<char*> values = {(char*)&val};
+        session->insertRecord(deviceId, time, measurements, types, values);
+    }
+
+    unique_ptr<SessionDataSet> sessionDataSet = 
session->executeQueryStatement("select s1 from root.test.d1");
+    auto dataIter = sessionDataSet->getIterator();
+    sessionDataSet->setFetchSize(1024);
+    int count = 0;
+    while (dataIter.next()) {
+        bool expected = (count % 2 == 0);
+        // Column 1 is Time, column 2 is s1
+        REQUIRE(dataIter.getBooleanByIndex(2).value() == expected);
+        // Accessing time column (index 1) as boolean should throw
+        REQUIRE_THROWS_AS(dataIter.getBooleanByIndex(1), IoTDBException);
+        count++;
+    }
+    REQUIRE(count == 10);
+    session->deleteTimeseries(timeseries);
+}
+
 TEST_CASE("Test Last query ", "[testLastQuery]") {
     CaseReporter cr("testLastQuery");
     prepareTimeseries();

Reply via email to