Skip to content

Fix unsafe vec.data() usage where nullptr was assumed for empty vectors#203

Open
Copilot wants to merge 4 commits into
masterfrom
copilot/audit-empty-vector-data-usage
Open

Fix unsafe vec.data() usage where nullptr was assumed for empty vectors#203
Copilot wants to merge 4 commits into
masterfrom
copilot/audit-empty-vector-data-usage

Conversation

Copilot AI commented May 1, 2026

Copy link
Copy Markdown
Contributor
  • Audit the entire repository for std::vector::data() antipatterns
  • Fix include/glk/type_conversion.hpp: Change both convert_to_vector overloads from if (points == nullptr) to if (num_points <= 0)
  • Fix include/glk/pointcloud_buffer.hpp: Guard [0].data() UB in add_normals, add_buffer template methods
  • Fix include/glk/pointcloud_buffer_pcl.hpp: Guard [0].data() UB in PCL cloud helpers
  • Fix include/glk/lines.hpp: Pass nullptr instead of colors.data()/infos.data() when those vectors are empty
  • Fix include/glk/thin_lines.hpp: Pass nullptr instead of colors.data()/cmap.data() when empty
  • Fix include/guik/viewer/light_viewer_context.hpp: Guard colors.data() in vector-taking overloads; fix convert_to_vector color path
  • Fix include/guik/viewer/async_light_viewer_context.hpp: Same fixes for async variants
  • Fix src/glk/pointcloud_buffer.cpp: Guard ->data() and [0].data() UB in constructors and methods
  • Fix src/glk/thin_lines.cpp: Guard vertices->data() with num_vertices > 0 checks; also guard convert_scalars temporary .data() on lines 91/94
  • Fix src/glk/normal_distributions.cpp: Guard means[0].data()/covs[0].data() with num_points > 0
  • Fix src/glk/glsl_shader.cpp: Replace vectors[0].data() with reinterpret_cast<const float*>(vectors.data())
  • Fix src/glk/pointnormals_buffer.cpp: Guard vertices[0].data()/normals[0].data() with empty() checks
  • Addressed code review feedback: guarded convert_scalars temporary .data() and clarified num_points <= 0 check in add_intensity
  • Added braces to all single-line if statements and applied clang-format to all changed files
Original prompt

Problem

std::vector<T>::data() is not guaranteed to return nullptr when the vector is empty. The C++ standard only guarantees that the returned pointer points to the first element if size() > 0; otherwise the return value may be any value (including a non-null sentinel pointer used by the allocator). Any code that calls vec.data() and then checks the result against nullptr (or passes it to a function that interprets nullptr as "no data") is therefore buggy and the bug only manifests with certain standard library implementations or after the vector has been resized down.

Please audit the entire koide3/iridescence repository for this antipattern and fix every occurrence.

What to look for

Search both include/ and src/ (and any examples) for patterns such as:

  1. something_vec.data() being passed to an API where the receiving code checks the pointer for nullptr to decide whether to read it.
  2. Constructs like:
    void foo(const T* data, int n);
    foo(vec.data(), vec.size());
    where foo itself does if (data == nullptr) { ... handle empty ... } instead of relying on n == 0.
  3. Code that conditionally calls vec.data() and assigns the result, then later checks the pointer to decide whether the vector had elements.
  4. Functions that accept (const T* data, int num) and treat data == nullptr as a special "empty / allocate uninitialized" case (e.g. in include/glk/type_conversion.hpp, the convert_to_vector(const Eigen::Matrix<...>* points, int num_points) overloads have an early-return branch if (points == nullptr) { return std::vector<...>(num_points); }). When such a function is called as convert_to_vector(some_vec.data(), some_vec.size()) with an empty some_vec, the early-return branch may or may not trigger depending on the implementation, leading to non-deterministic behavior. Any caller passing vec.data() to such APIs is at risk.
  5. Similar patterns around OpenGL / GPU buffer uploads where vec.data() is used and the buffer code interprets nullptr specifically (e.g. glBufferData, custom set_data(size_t, const void*), ShaderStorageBuffer, vertex/index buffers, etc.). Calling these with vec.data() of an empty vector may upload from a garbage non-null pointer.
  6. Eigen Eigen::Map<...>(vec[0].data(), ...) or vec.data()->data() - these are undefined behavior when the vector is empty, regardless of the nullptr issue. While auditing, also flag and guard these with an empty() check.

How to fix

For each occurrence, choose the most appropriate fix:

  • Preferred: Add an early if (vec.empty()) { return ...; } guard at the call site before dereferencing or passing vec.data().
  • For APIs that take (ptr, size): make the implementation rely on size == 0 rather than ptr == nullptr to detect "no data". Update the API's internal if (ptr == nullptr) checks to if (num <= 0) (or ||-combine them defensively).
  • At call sites, prefer passing vec.empty() ? nullptr : vec.data() only when the receiving API contractually accepts nullptr as "no data" AND that contract is documented. Otherwise, simply guard the entire call with if (!vec.empty()).
  • For Eigen::Map constructions over vec[0].data(), wrap in if (!vec.empty()) so the map is only built when there is at least one element.

Concrete starting points (non-exhaustive — please search the whole repo)

  • include/glk/type_conversion.hpp — the two convert_to_vector(const Eigen::Matrix<...>* points, int num_points) overloads (around lines 50–93) check if (points == nullptr) to decide whether to return a default-constructed vector. Any caller doing convert_to_vector(vec.data(), vec.size()) on a possibly-empty vec is unsafe. Fix the function to use num_points <= 0 as the empty condition, and additionally guard the Eigen::Map<...>(converted[0].data(), ...) / points[0].data() accesses with a num_points <= 0 early-return so we never index points[0] or converted[0] when the size is zero.
  • include/glk/type_conversion.hppconvert_scalars(const Src* data, int num_data) does std::copy(data, data + num_data, ...). If a caller passes vec.data() for an empty vec, data could be a non-null garbage pointer; std::copy with num_data == 0 is fine, but if any caller separately checks for nullptr, it is not. Audit callers.
  • Search every *.cpp / *.hpp under src/ and include/ for the regex \.data\(\) and review each call. Particularly look in:
    • src/glk/*.cpp (vertex buffers, index buffers, shader_storage_buffer.cpp, texture.cpp, mesh*.cpp, pointcloud_buffer.cpp, thin_lines.cpp, lines.cpp, normal_distributions.cpp, etc.)
    • src/guik/**/*.cpp (e.g. plot_data.cpp plotting xs.data(), ys.data())
    • Any make_* / loader helpers that forward vec.data() to a (ptr, size) API.
  • Also grep for points[0].data(), vec[0].data(), and &vec[0] patterns — these are UB on empty vectors and should be guard...

This pull request was created from Copilot chat.

Copilot AI and others added 2 commits May 1, 2026 09:52
…e checks and guard [0] accesses

Audit and fix all occurrences of std::vector<T>::data() being used in ways
that incorrectly assume the returned pointer is nullptr for empty vectors.

Changes by file:
- include/glk/type_conversion.hpp: Change both convert_to_vector overloads
  from if (points == nullptr) to if (num_points <= 0) so empty-vector callers
  are handled correctly regardless of what data() returns.

- include/glk/pointcloud_buffer.hpp: Guard [0].data() and ->data() accesses
  in add_normals (float and double), add_normals (raw pointer), and add_buffer
  template methods with empty() or num_points > 0 checks.

- include/glk/pointcloud_buffer_pcl.hpp: Guard [0].data() UB in
  create_point_cloud_buffer and create_colored_point_cloud_buffer for empty
  PCL clouds.

- include/glk/lines.hpp: Pass nullptr instead of colors.data() / infos.data()
  when those vectors are empty in the vector-taking template constructors.

- include/glk/thin_lines.hpp: Pass nullptr instead of colors.data() /
  cmap.data() when empty in vector-taking template constructors.

- include/guik/viewer/light_viewer_context.hpp: Guard colors.data() in
  vector-taking update_thin_lines overloads; fix convert_to_vector color
  path to respect nullptr input and use size-based empty check.

- include/guik/viewer/async_light_viewer_context.hpp: Same fixes as
  light_viewer_context.hpp for async variants of update_points and
  update_thin_lines.

- src/glk/pointcloud_buffer.cpp: Guard ->data() and [0].data() UB in
  Eigen::Vector{3,4}f constructors, add_color, add_intensity, and
  update_{points,color}_with_indices.

- src/glk/thin_lines.cpp: Guard vertices->data() and colors->data() with
  num_vertices > 0 checks in all Eigen::Vector3f* constructors.

- src/glk/normal_distributions.cpp: Guard means[0].data()/covs[0].data()
  with num_points > 0 in the float,4 template specialization.

- src/glk/glsl_shader.cpp: Replace vectors[0].data() with
  reinterpret_cast<const float*>(vectors.data()) in set_uniform overloads
  for Vector2/3/4f to avoid UB on empty vectors.

- src/glk/pointnormals_buffer.cpp: Guard vertices[0].data()/normals[0].data()
  with empty() checks in the vector-taking template constructor.

Agent-Logs-Url: https://github.com/koide3/iridescence/sessions/2970d6ba-4151-4f2b-8d86-67e1548a9c3a

Co-authored-by: koide3 <31344317+koide3@users.noreply.github.com>
…ify num_points check in add_intensity

Agent-Logs-Url: https://github.com/koide3/iridescence/sessions/2970d6ba-4151-4f2b-8d86-67e1548a9c3a

Co-authored-by: koide3 <31344317+koide3@users.noreply.github.com>
Copilot AI changed the title [WIP] Audit and fix misuse of std::vector<T>::data() in repository Fix unsafe vec.data() usage where nullptr was assumed for empty vectors May 1, 2026
Copilot finished work on behalf of koide3 May 1, 2026 09:57
Copilot AI requested a review from koide3 May 1, 2026 09:57

@koide3 koide3 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please always use brackets for if clauses even if it's one line. Please also apply clang-format.

Copilot AI commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

Please always use brackets for if clauses even if it's one line. Please also apply clang-format.

Done in df4d4b6. Added braces to all single-line if statements introduced by this PR and ran clang-format on all 12 changed files.

Copilot finished work on behalf of koide3 May 1, 2026 12:59
Copilot AI requested a review from koide3 May 1, 2026 12:59
@koide3 koide3 marked this pull request as ready for review May 1, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants