Fix timeSamples pprint for array types and add bounds checking

- Fix move constructor/assignment operator to transfer value array storage
  fields (_value_array_storage, _value_array_refs, _use_value_array)
- Fix copy constructor/assignment operator for value array storage fields
- Add bounds checking for blocked[] and offsets[] array access in pprint
- Strip TYPE_ID_1D_ARRAY_BIT before type lookup in print_pod_value_dispatch
  and get_pod_type_size to handle array element types correctly
- Add handler for value array storage path in get_samples()
- Set _use_pod=false when using value array storage

This fixes the "Unknown type_id: 1048618" error when printing array-typed
timeSamples (e.g., color3f[]) and prevents heap-buffer-overflow when
printing timeSamples with None values.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Syoyo Fujita
2026-01-08 07:02:27 +09:00
parent c6ace402dc
commit 8834878173
4 changed files with 102 additions and 7 deletions

View File

@@ -0,0 +1,15 @@
curdir=`pwd`
builddir=${curdir}/build_asan
rm -rf ${builddir}
mkdir ${builddir}
# with lld linker
# -DCMAKE_TOOLCHAIN_FILE=cmake/lld-linux.toolchain.cmake
cd ${builddir} && CXX=clang++ CC=clang cmake \
-DSANITIZE_ADDRESS=1 \
-DCMAKE_VERBOSE_MAKEFILE=1 \
..

View File

@@ -311,6 +311,9 @@ void print_vector<uint8_t, 4>(OutputAdapter& out, const uint8_t* data) {
void print_pod_value_dispatch(OutputAdapter& out, const uint8_t* data, uint32_t type_id) {
using namespace value;
// Strip array bit - we're printing a single element
type_id = type_id & (~TYPE_ID_1D_ARRAY_BIT);
switch (type_id) {
DISPATCH_POD_TYPE(TYPE_ID_BOOL, bool, print_type)
DISPATCH_POD_TYPE(TYPE_ID_CHAR, char, print_type)
@@ -1490,6 +1493,9 @@ void pprint_pod_value_by_type(StreamWriter& writer, const uint8_t* data, uint32_
size_t get_pod_type_size(uint32_t type_id) {
using namespace value;
// Strip array bit - we want the element size
type_id = type_id & (~TYPE_ID_1D_ARRAY_BIT);
switch (type_id) {
case TYPE_ID_BOOL:
return sizeof(bool);
@@ -2326,10 +2332,6 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
const auto& offsets = samples.get_offsets();
const auto& array_counts = samples.get_array_counts();
//TUSDZ_LOG_I("times.size " << times.size());
//TUSDZ_LOG_I("blocked.size " << blocked.size());
//TUSDZ_LOG_I("values.size " << values.size());
// Write samples - handle offset table if present
if (!offsets.empty()) {
@@ -2340,7 +2342,11 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
writer.write(times[i]);
writer.write(": ");
if (blocked[i] || offsets[i] == SIZE_MAX) {
// Check blocked array bounds before accessing
bool is_blocked = (i < blocked.size()) ? blocked[i] : false;
// Check offsets bounds as well - treat out-of-bounds as None
bool offset_is_none = (i >= offsets.size()) || (offsets[i] == SIZE_MAX);
if (is_blocked || offset_is_none) {
writer.write("None");
} else {
// Resolve offset (may be encoded with dedup/array flags) and get resolved index
@@ -2374,6 +2380,18 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
}
} else {
// Legacy: blocked values still counted in offset calculation
// Handle case where values is empty but times is not
if (values.empty() && !times.empty()) {
for (size_t i = 0; i < times.size(); ++i) {
writer.write(pprint::Indent(indent + 1));
writer.write(times[i]);
writer.write(": /* empty value data */");
if (i < times.size() - 1) {
writer.write(",");
}
writer.write("\n");
}
} else {
size_t value_offset = 0;
for (size_t i = 0; i < times.size(); ++i) {
//TUSDZ_LOG_I("times[" << i << "] = " << times[i]);
@@ -2381,7 +2399,9 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
writer.write(times[i]);
writer.write(": ");
if (blocked[i]) {
// Check blocked array bounds before accessing
bool is_blocked = (i < blocked.size()) ? blocked[i] : false;
if (is_blocked) {
writer.write("None");
} else {
// Get pointer to value data
@@ -2407,6 +2427,7 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
}
writer.write("\n");
}
} // end else for values.empty() check
}
} else {
// Non-POD path: use regular samples

View File

@@ -1600,8 +1600,11 @@ TimeSamples::TimeSamples(TimeSamples&& other) noexcept
_small_values(std::move(other._small_values)),
_values(std::move(other._values)),
_offsets(std::move(other._offsets)),
_value_array_storage(std::move(other._value_array_storage)),
_value_array_refs(std::move(other._value_array_refs)),
_type_id(other._type_id),
_use_pod(other._use_pod),
_use_value_array(other._use_value_array),
_is_array(other._is_array),
_array_size(other._array_size),
_element_size(other._element_size),
@@ -1613,6 +1616,7 @@ TimeSamples::TimeSamples(TimeSamples&& other) noexcept
// Reset moved-from object to valid empty state
other._type_id = 0;
other._use_pod = false;
other._use_value_array = false;
other._is_array = false;
other._array_size = 0;
other._element_size = 0;
@@ -1634,8 +1638,11 @@ TimeSamples& TimeSamples::operator=(TimeSamples&& other) noexcept {
_offsets = std::move(other._offsets);
_pod_samples = std::move(other._pod_samples);
_small_values = std::move(other._small_values);
_value_array_storage = std::move(other._value_array_storage);
_value_array_refs = std::move(other._value_array_refs);
_type_id = other._type_id;
_use_pod = other._use_pod;
_use_value_array = other._use_value_array;
_is_array = other._is_array;
_array_size = other._array_size;
_element_size = other._element_size;
@@ -1647,6 +1654,7 @@ TimeSamples& TimeSamples::operator=(TimeSamples&& other) noexcept {
// Reset moved-from object to valid empty state
other._type_id = 0;
other._use_pod = false;
other._use_value_array = false;
other._is_array = false;
other._array_size = 0;
other._element_size = 0;
@@ -1666,8 +1674,11 @@ TimeSamples::TimeSamples(const TimeSamples& other)
_small_values(other._small_values),
_values(other._values),
_offsets(other._offsets),
// _value_array_storage is copied in body to avoid TypeTraits issues
_value_array_refs(other._value_array_refs),
_type_id(other._type_id),
_use_pod(other._use_pod),
_use_value_array(other._use_value_array),
_is_array(other._is_array),
_array_size(other._array_size),
_element_size(other._element_size),
@@ -1676,6 +1687,11 @@ TimeSamples::TimeSamples(const TimeSamples& other)
_dirty_start(other._dirty_start),
_dirty_end(other._dirty_end),
_pod_samples(other._pod_samples) {
// Copy value array storage in body to avoid TypeTraits instantiation issues
_value_array_storage.reserve(other._value_array_storage.size());
for (const auto& v : other._value_array_storage) {
_value_array_storage.push_back(v);
}
// Deep copy _array_values (vector of unique_ptr)
_array_values.clear();
_array_values.reserve(other._array_values.size());
@@ -1700,6 +1716,7 @@ TimeSamples& TimeSamples::operator=(const TimeSamples& other) {
_offsets = other._offsets;
_type_id = other._type_id;
_use_pod = other._use_pod;
_use_value_array = other._use_value_array;
_is_array = other._is_array;
_array_size = other._array_size;
_element_size = other._element_size;
@@ -1709,6 +1726,13 @@ TimeSamples& TimeSamples::operator=(const TimeSamples& other) {
_dirty_end = other._dirty_end;
_pod_samples = other._pod_samples;
_small_values = other._small_values;
_value_array_refs = other._value_array_refs;
// Copy value array storage element by element to avoid TypeTraits instantiation issues
_value_array_storage.clear();
_value_array_storage.reserve(other._value_array_storage.size());
for (const auto& v : other._value_array_storage) {
_value_array_storage.push_back(v);
}
// Deep copy _array_values (vector of unique_ptr)
_array_values.clear();

View File

@@ -1391,6 +1391,7 @@ struct TimeSamples {
_type_id = v.type_id();
_use_value_array = true;
_is_array = true;
_use_pod = false; // Value array storage is not POD storage
}
// Store in value array storage
@@ -1989,7 +1990,8 @@ struct TimeSamples {
}
// If _use_pod = false but unified storage has data, convert to generic samples
if (!_times.empty() && _samples.empty()) {
// Skip if using value array storage - that's handled below
if (!_use_value_array && !_times.empty() && _samples.empty()) {
if (_dirty) {
update();
}
@@ -2113,6 +2115,39 @@ struct TimeSamples {
return _samples;
}
// Handle value array storage (from add_value_array_sample)
if (_use_value_array && !_times.empty() && _samples.empty()) {
if (_dirty) {
update();
}
_samples.clear();
_samples.reserve(_times.size());
for (size_t i = 0; i < _times.size(); ++i) {
Sample s;
s.t = _times[i];
// Check dedup flag from _value_array_refs
if (i < _value_array_refs.size()) {
size_t storage_idx = get_value_array_index(_value_array_refs[i]);
if (storage_idx < _value_array_storage.size()) {
s.value = _value_array_storage[storage_idx];
s.blocked = false;
} else {
s.value = value::Value(); // Invalid index
s.blocked = true;
}
} else {
s.value = value::Value();
s.blocked = true;
}
_samples.push_back(s);
}
return _samples;
}
if (_dirty) {
update();
}