Skip to content

Commit 97a3d3b

Browse files
fix: address all code review issues (HIGH/MEDIUM/LOW)
- H1: fix integer overflow in assertImage (int→size_t for pixel counts) - H2: fix localtime_s portability (add localtime_r for POSIX) - H3: remove misleading std::once_flag (single-threaded server) - M1: reject tools/list and tools/call before initialization (-32002) - M2: throw on duplicate tool name registration - M3: reject unknown parameters in validateArgs - M4: remove dead null checks after controller() (already throws) - M5: replace hand-crafted JSON in snapshot.cpp with nlohmann::json - M6: nest AssertResult details under "details" key - M7: isolate stb_image IMPLEMENTATION into dedicated TU - L1: check std::cout.fail() to detect broken output pipe - L2: fix #include order in main.cpp - L3: tighten findNewestCapture filename matching - L4: make test stub controller() throw like real implementation - L5: add batch edge case tests (non-object elements, all-notifications) - L6: add ShaderEditState::clear() method Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fa66161 commit 97a3d3b

17 files changed

Lines changed: 151 additions & 95 deletions

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ if(RENDERDOC_DIR)
4444
src/core/snapshot.cpp
4545
src/core/usage.cpp
4646
src/core/assertions.cpp
47+
src/core/stb_impl.cpp
4748
src/core/diff_session.cpp
4849
src/core/diff_alignment.cpp
4950
src/core/diff_structure.cpp

src/core/assertions.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
#include <memory>
1212
#include <sstream>
1313

14-
#define STB_IMAGE_IMPLEMENTATION
1514
#include "stb_image.h"
16-
17-
#define STB_IMAGE_WRITE_IMPLEMENTATION
1815
#include "stb_image_write.h"
1916

2017
#include <renderdoc_replay.h>
@@ -108,13 +105,13 @@ ImageCompareResult assertImage(const std::string& expectedPath,
108105
"Image size mismatch: " + std::to_string(ew) + "x" + std::to_string(eh) +
109106
" vs " + std::to_string(aw) + "x" + std::to_string(ah));
110107

111-
int totalPixels = ew * eh;
112-
int diffPixels = 0;
108+
size_t totalPixels = static_cast<size_t>(ew) * static_cast<size_t>(eh);
109+
size_t diffPixels = 0;
113110
std::vector<unsigned char> diffImage;
114111
if (!diffOutputPath.empty()) diffImage.resize(totalPixels * 4);
115112

116-
for (int i = 0; i < totalPixels; ++i) {
117-
int offset = i * 4;
113+
for (size_t i = 0; i < totalPixels; ++i) {
114+
size_t offset = i * 4;
118115
bool same = (expectedData[offset] == actualData[offset] &&
119116
expectedData[offset+1] == actualData[offset+1] &&
120117
expectedData[offset+2] == actualData[offset+2] &&

src/core/capture.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ std::string generateOutputPath(const std::string& exePath) {
3535
auto now = std::chrono::system_clock::now();
3636
auto timeT = std::chrono::system_clock::to_time_t(now);
3737
std::tm tm{};
38+
#ifdef _WIN32
3839
localtime_s(&tm, &timeT);
40+
#else
41+
localtime_r(&timeT, &tm);
42+
#endif
3943

4044
char buf[32];
4145
std::strftime(buf, sizeof(buf), "%Y%m%d_%H%M%S", &tm);
@@ -71,7 +75,11 @@ std::string findNewestCapture(const std::string& exeName,
7175
for (const auto& entry : fs::directory_iterator(dir)) {
7276
if (entry.path().extension() != ".rdc") continue;
7377
auto name = entry.path().filename().string();
74-
if (name.find(exeName) == std::string::npos) continue;
78+
// Match filenames that start with exeName or contain exeName preceded by
79+
// a separator, to avoid overly broad substring matches.
80+
auto pos = name.find(exeName);
81+
if (pos == std::string::npos) continue;
82+
if (pos != 0 && name[pos - 1] != '_' && name[pos - 1] != '-') continue;
7583
auto ftime = entry.last_write_time();
7684
if (found.empty() || ftime > newestTime) {
7785
found = entry.path().string();

src/core/export.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ ExportResult exportRenderTarget(const Session& session,
4040
const std::string& outputDir)
4141
{
4242
auto* ctrl = session.controller();
43-
if (!ctrl)
44-
throw CoreError(CoreError::Code::NoCaptureOpen,
45-
"No capture is open. Call open_capture first.");
4643

4744
// Walk the action tree to find the current event and retrieve its output RT.
4845
const uint32_t eventId = session.currentEventId();
@@ -123,9 +120,6 @@ ExportResult exportTexture(const Session& session,
123120
uint32_t layer)
124121
{
125122
auto* ctrl = session.controller();
126-
if (!ctrl)
127-
throw CoreError(CoreError::Code::NoCaptureOpen,
128-
"No capture is open. Call open_capture first.");
129123

130124
::ResourceId rdcId = fromResourceId(id);
131125

@@ -164,9 +158,6 @@ ExportResult exportBuffer(const Session& session,
164158
uint64_t size)
165159
{
166160
auto* ctrl = session.controller();
167-
if (!ctrl)
168-
throw CoreError(CoreError::Code::NoCaptureOpen,
169-
"No capture is open. Call open_capture first.");
170161

171162
::ResourceId rdcId = fromResourceId(id);
172163
rdcarray<byte> data = ctrl->GetBufferData(rdcId, offset, size);

src/core/session.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ Session::~Session() {
1717
}
1818

1919
void Session::ensureReplayInitialized() {
20-
std::call_once(m_replayInitFlag, [this]() {
21-
GlobalEnvironment env;
22-
memset(&env, 0, sizeof(env));
23-
rdcarray<rdcstr> args;
24-
RENDERDOC_InitialiseReplay(env, args);
25-
m_replayInitialized = true;
26-
});
20+
if (m_replayInitialized)
21+
return;
22+
GlobalEnvironment env;
23+
memset(&env, 0, sizeof(env));
24+
rdcarray<rdcstr> args;
25+
RENDERDOC_InitialiseReplay(env, args);
26+
m_replayInitialized = true;
2727
}
2828

2929
void Session::closeCurrent() {
@@ -40,7 +40,7 @@ void Session::closeCurrent() {
4040
m_capturePath.clear();
4141
m_totalEvents = 0;
4242
m_api = GraphicsApi::Unknown;
43-
m_shaderEditState = ShaderEditState{};
43+
m_shaderEditState.clear();
4444
}
4545

4646
void Session::close() {

src/core/session.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
#include "core/types.h"
44
#include "core/shader_edit.h"
5-
#include <mutex>
65
#include <string>
76

87
// Forward declarations from RenderDoc
@@ -46,7 +45,6 @@ class Session {
4645
IReplayController* m_controller = nullptr;
4746
uint32_t m_currentEventId = 0;
4847
std::string m_capturePath;
49-
std::once_flag m_replayInitFlag;
5048
bool m_replayInitialized = false;
5149
uint32_t m_totalEvents = 0;
5250
GraphicsApi m_api = GraphicsApi::Unknown;

src/core/shader_edit.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ class Session;
1313
struct ShaderEditState {
1414
std::map<uint64_t, uint64_t> builtShaders;
1515
std::map<uint64_t, uint64_t> shaderReplacements;
16+
17+
void clear() {
18+
builtShaders.clear();
19+
shaderReplacements.clear();
20+
}
1621
};
1722

1823
std::vector<std::string> getShaderEncodings(const Session& session);

src/core/snapshot.cpp

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <chrono>
1313
#include <filesystem>
1414
#include <fstream>
15+
#include <nlohmann/json.hpp>
1516
#include <sstream>
1617

1718
namespace fs = std::filesystem;
@@ -20,31 +21,6 @@ namespace renderdoc::core {
2021

2122
namespace {
2223

23-
// Escape a string for JSON output (handles quotes, backslashes, control chars).
24-
std::string jsonEscape(const std::string& s) {
25-
std::string out;
26-
out.reserve(s.size() + 8);
27-
for (char c : s) {
28-
switch (c) {
29-
case '"': out += "\\\""; break;
30-
case '\\': out += "\\\\"; break;
31-
case '\n': out += "\\n"; break;
32-
case '\r': out += "\\r"; break;
33-
case '\t': out += "\\t"; break;
34-
default:
35-
if (static_cast<unsigned char>(c) < 0x20) {
36-
char buf[8];
37-
snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned>(c));
38-
out += buf;
39-
} else {
40-
out += c;
41-
}
42-
break;
43-
}
44-
}
45-
return out;
46-
}
47-
4824
// Map ShaderStage to a short string for filenames.
4925
std::string stageToFileSuffix(ShaderStage stage) {
5026
switch (stage) {
@@ -64,9 +40,6 @@ SnapshotResult exportSnapshot(Session& session, uint32_t eventId,
6440
const std::string& outputDir,
6541
std::function<std::string(const PipelineState&)> pipelineSerializer) {
6642
auto* ctrl = session.controller();
67-
if (!ctrl)
68-
throw CoreError(CoreError::Code::NoCaptureOpen,
69-
"No capture is open. Call open_capture first.");
7043

7144
ctrl->SetFrameEvent(eventId, true);
7245

@@ -180,42 +153,28 @@ SnapshotResult exportSnapshot(Session& session, uint32_t eventId,
180153
}
181154
}
182155

183-
// 5. Write manifest.json (hand-crafted, no nlohmann::json dependency).
156+
// 5. Write manifest.json.
184157
{
185158
auto now = std::chrono::system_clock::now();
186159
auto epoch = std::chrono::duration_cast<std::chrono::seconds>(
187160
now.time_since_epoch()).count();
188161

189-
std::ostringstream manifest;
190-
manifest << "{\n";
191-
manifest << " \"eventId\": " << eventId << ",\n";
192-
manifest << " \"timestamp\": " << epoch << ",\n";
193-
manifest << " \"files\": [";
194-
195-
for (size_t i = 0; i < result.files.size(); i++) {
196-
if (i > 0) manifest << ",";
197-
// Use just the filename, not the full path.
198-
std::string fname = fs::path(result.files[i]).filename().string();
199-
manifest << "\n \"" << jsonEscape(fname) << "\"";
200-
}
201-
202-
manifest << "\n ]";
162+
nlohmann::json manifest;
163+
manifest["eventId"] = eventId;
164+
manifest["timestamp"] = epoch;
203165

204-
if (!result.errors.empty()) {
205-
manifest << ",\n \"errors\": [";
206-
for (size_t i = 0; i < result.errors.size(); i++) {
207-
if (i > 0) manifest << ",";
208-
manifest << "\n \"" << jsonEscape(result.errors[i]) << "\"";
209-
}
210-
manifest << "\n ]";
211-
}
166+
auto filesArr = nlohmann::json::array();
167+
for (const auto& f : result.files)
168+
filesArr.push_back(fs::path(f).filename().string());
169+
manifest["files"] = filesArr;
212170

213-
manifest << "\n}\n";
171+
if (!result.errors.empty())
172+
manifest["errors"] = result.errors;
214173

215174
std::string manifestPath = (fs::path(outputDir) / "manifest.json").string();
216175
std::ofstream ofs(manifestPath);
217176
if (ofs) {
218-
ofs << manifest.str();
177+
ofs << manifest.dump(2);
219178
ofs.close();
220179
result.manifestPath = manifestPath;
221180
result.files.push_back(manifestPath);

src/core/stb_impl.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Dedicated translation unit for stb_image implementations.
2+
// These macros must appear in exactly one .cpp file.
3+
4+
#define STB_IMAGE_IMPLEMENTATION
5+
#include "stb_image.h"
6+
7+
#define STB_IMAGE_WRITE_IMPLEMENTATION
8+
#include "stb_image_write.h"

src/core/types.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,8 @@ struct PixelAssertResult {
403403

404404
struct ImageCompareResult {
405405
bool pass = false;
406-
int diffPixels = 0;
407-
int totalPixels = 0;
406+
size_t diffPixels = 0;
407+
size_t totalPixels = 0;
408408
double diffRatio = 0.0;
409409
std::string diffOutputPath;
410410
std::string message;

0 commit comments

Comments
 (0)