Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F84589881
D309.1781323073.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
23 KB
Referenced Files
None
Subscribers
None
D309.1781323073.diff
View Options
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -35,7 +35,7 @@
- mkdir -pv "$TMPDIR"
- chown -R podman:podman "$TMPDIR"
- sudo -u podman TMPDIR="$TMPDIR" podman login -u "$REGISTRY_USER" -p "$REGISTRY_PASSWORD" "$REGISTRY"
- - sudo -u podman TMPDIR="$TMPDIR" podman build -f "$CI_PROJECT_DIR"/Containerfile --build-arg JOBS=3 --build-arg BASE_IMG_TAG=$BASE_IMG_TAG --build-arg BUILD_TYPE=$BUILD_TYPE $BUILD_ARGS -t "$CANONICAL_TAG" "$CI_PROJECT_DIR"
+ - sudo -u podman TMPDIR="$TMPDIR" podman build -f "$CI_PROJECT_DIR"/Containerfile --build-arg JOBS=3 --build-arg BASE_IMG_TAG=$BASE_IMG_TAG --build-arg BUILD_TYPE=$BUILD_TYPE --build-arg LIBKAZV_ERROR_ON_WARNING=OFF $BUILD_ARGS -t "$CANONICAL_TAG" "$CI_PROJECT_DIR"
# Only branch and tag commits should trigger pushing
- |
if [ -z "$CI_COMMIT_BRANCH" -a -z "$CI_COMMIT_TAG" ]; then
@@ -98,7 +98,7 @@
ccache --show-stats || true
mkdir build && cd build && \
cmake .. -DCMAKE_INSTALL_PREFIX="$LIBKAZV_INSTALL_DIR" -DCMAKE_PREFIX_PATH="$DEPS_INSTALL_DIR" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -Dlibkazv_BUILD_TESTS=ON \
- -Dlibkazv_BUILD_EXAMPLES=ON -Dlibkazv_BUILD_KAZVJOB=ON -Dlibkazv_ENABLE_COVERAGE=ON -DCMAKE_CXX_FLAGS=-fsanitize=address && \
+ -Dlibkazv_BUILD_EXAMPLES=ON -Dlibkazv_BUILD_KAZVJOB=ON -Dlibkazv_ENABLE_COVERAGE=ON -DCMAKE_CXX_FLAGS=-fsanitize=address -Dlibkazv_ERROR_ON_WARNING=OFF && \
make -j$JOBS && \
make CTEST_OUTPUT_ON_FAILURE=1 test && \
gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml -r "${CI_PROJECT_DIR}" --object-directory src -e '.*/api/.*' -e '.*/tests/.*' -e '.*/testfixtures/.*' -e '.*/examples/.*'
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,11 +26,17 @@
option(libkazv_BUILD_KAZVJOB "Build libkazvjob the async and networking library" ON)
option(libkazv_OUTPUT_LEVEL "Output level: Debug>=90, Info>=70, Quiet>=20, no output=1" 0)
option(libkazv_ENABLE_COVERAGE "Enable code coverage information" OFF)
+option(libkazv_ERROR_ON_WARNING "Turn compiler warnings to errors. Only use for development purposes." OFF)
if(libkazv_ENABLE_COVERAGE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -fPIC -O0")
endif()
+if(libkazv_ERROR_ON_WARNING)
+ message(STATUS "Turning compiler warnings to errors. This should only be used for development purposes.")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
+endif()
+
if((libkazv_BUILD_TESTS OR libkazv_BUILD_EXAMPLES) AND NOT libkazv_BUILD_KAZVJOB)
message(FATAL_ERROR
"You asked kazvjob not to be built, but asked to build tests or examples. Tests and examples both depend on kazvjob. This is not possible.")
diff --git a/Containerfile b/Containerfile
--- a/Containerfile
+++ b/Containerfile
@@ -11,6 +11,7 @@
ARG DEPS_INSTALL_DIR=/opt/libkazv-deps
ARG LIBKAZV_INSTALL_DIR=/opt/libkazv
+ARG LIBKAZV_ERROR_ON_WARNING=OFF
RUN --mount=type=cache,id=ccache,target=/ccache \
export CCACHE_COMPILERCHECK=content \
@@ -20,7 +21,7 @@
CC=/usr/lib/ccache/gcc CXX=/usr/lib/ccache/g++ && \
ccache --zero-stats; ccache --show-stats; \
mkdir build && cd build && \
- cmake .. -DCMAKE_INSTALL_PREFIX="$LIBKAZV_INSTALL_DIR" -DCMAKE_PREFIX_PATH="$DEPS_INSTALL_DIR" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -Dlibkazv_BUILD_TESTS=ON -Dlibkazv_BUILD_EXAMPLES=ON -Dlibkazv_BUILD_KAZVJOB=ON && \
+ cmake .. -DCMAKE_INSTALL_PREFIX="$LIBKAZV_INSTALL_DIR" -DCMAKE_PREFIX_PATH="$DEPS_INSTALL_DIR" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -Dlibkazv_BUILD_TESTS=ON -Dlibkazv_BUILD_EXAMPLES=ON -Dlibkazv_BUILD_KAZVJOB=ON -Dlibkazv_ERROR_ON_WARNING="$LIBKAZV_ERROR_ON_WARNING" && \
make -j$JOBS && \
make test && \
make -j$JOBS install && \
diff --git a/src/client/actions/auth.cpp b/src/client/actions/auth.cpp
--- a/src/client/actions/auth.cpp
+++ b/src/client/actions/auth.cpp
@@ -182,7 +182,7 @@
m.versions = r.versions();
return {
std::move(m),
- [r](auto &&ctx) {
+ [r]([[maybe_unused]] auto &&ctx) {
return EffectStatus(r.success(), json{
{"versions", r.versions()},
});
diff --git a/src/client/client-model.cpp b/src/client/client-model.cpp
--- a/src/client/client-model.cpp
+++ b/src/client/client-model.cpp
@@ -382,7 +382,7 @@
if (! changedUsers.empty()) {
for (auto roomId : roomIds) {
auto it = std::find_if(changedUsers.begin(), changedUsers.end(),
- [=](auto userId) { return roomList.rooms[roomId].hasUser(userId); });
+ [=, this](auto userId) { return roomList.rooms[roomId].hasUser(userId); });
if (it != changedUsers.end()) {
kzo.client.dbg() << "rotate keys for room " << roomId << std::endl;
markRotate(roomId);
diff --git a/src/client/client.cpp b/src/client/client.cpp
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -193,7 +193,7 @@
auto Client::logout() const
-> PromiseT
{
- return stopSyncing().then([ctx=m_ctx] (auto stat) {
+ return stopSyncing().then([ctx=m_ctx] ([[maybe_unused]] auto stat) {
return ctx.dispatch(HardLogoutAction{});
});
}
diff --git a/src/client/push-rules-desc.cpp b/src/client/push-rules-desc.cpp
--- a/src/client/push-rules-desc.cpp
+++ b/src/client/push-rules-desc.cpp
@@ -69,7 +69,7 @@
{
boost::smatch m;
auto isStr = is.template get<std::string>();
- auto res = boost::regex_match(isStr, m, memberCountIsRegex);
+ boost::regex_match(isStr, m, memberCountIsRegex);
auto targetStr = m[2].str();
int target;
std::from_chars(targetStr.data(), targetStr.data() + targetStr.size(), target);
@@ -276,7 +276,7 @@
return false;
}
- PushAction PushRulesDescPrivate::handleRule(std::string ruleSetName, const json &rule, const Event &e, const RoomModel &room) const
+ PushAction PushRulesDescPrivate::handleRule([[maybe_unused]] std::string ruleSetName, const json &rule, [[maybe_unused]] const Event &e, [[maybe_unused]] const RoomModel &room) const
{
auto actions = rule.at("actions");
auto res = PushAction{
@@ -332,6 +332,6 @@
auto [ruleSetName, rule] = ruleOpt.value();
return m_d->handleRule(ruleSetName, rule, e, room);
}
- return {false};
+ return {false, std::nullopt, false};
}
}
diff --git a/src/client/room/local-echo.hpp b/src/client/room/local-echo.hpp
--- a/src/client/room/local-echo.hpp
+++ b/src/client/room/local-echo.hpp
@@ -34,7 +34,7 @@
namespace boost::serialization
{
template<class Archive>
- void save(Archive &ar, const Kazv::LocalEchoDesc &d, std::uint32_t const version)
+ void save(Archive &ar, const Kazv::LocalEchoDesc &d, [[maybe_unused]] std::uint32_t const version)
{
using namespace Kazv;
LocalEchoDesc::Status dummyStatus{LocalEchoDesc::Failed};
@@ -46,7 +46,7 @@
}
template<class Archive>
- void load(Archive &ar, Kazv::LocalEchoDesc &d, std::uint32_t const version)
+ void load(Archive &ar, Kazv::LocalEchoDesc &d, [[maybe_unused]] std::uint32_t const version)
{
using namespace Kazv;
LocalEchoDesc::Status dummyStatus{LocalEchoDesc::Failed};
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -305,12 +305,12 @@
return m_d->account.value()->max_number_of_one_time_keys();
}
- std::size_t Crypto::genOneTimeKeysRandomSize(int num)
+ std::size_t Crypto::genOneTimeKeysRandomSize([[maybe_unused]] int num)
{
return 0;
}
- void Crypto::genOneTimeKeysWithRandom(RandomData random, int num)
+ void Crypto::genOneTimeKeysWithRandom([[maybe_unused]] RandomData random, int num)
{
assert(random.size() >= genOneTimeKeysRandomSize(num));
@@ -595,12 +595,12 @@
for (auto [userId, devices] : keyMap) {
auto unknownDevices =
intoImmer(immer::flex_vector<std::string>{},
- zug::filter([=](auto kv) {
+ zug::filter([this](auto kv) {
auto [deviceId, theirCurve25519IdentityKey] = kv;
return m_d->knownSessions.find(theirCurve25519IdentityKey)
== m_d->knownSessions.end();
})
- | zug::map([=](auto kv) {
+ | zug::map([](auto kv) {
auto [deviceId, key] = kv;
return deviceId;
}),
diff --git a/src/crypto/session.cpp b/src/crypto/session.cpp
--- a/src/crypto/session.cpp
+++ b/src/crypto/session.cpp
@@ -20,11 +20,11 @@
}
SessionPrivate::SessionPrivate(OutboundSessionTag,
- RandomTag,
- RandomData random,
- CryptoPrivate &cryptoD,
- std::string theirIdentityKey,
- std::string theirOneTimeKey)
+ RandomTag,
+ [[maybe_unused]] RandomData random,
+ CryptoPrivate &cryptoD,
+ std::string theirIdentityKey,
+ std::string theirOneTimeKey)
: SessionPrivate()
{
assert(random.size() >= Session::constructOutboundRandomSize());
@@ -216,7 +216,7 @@
return 0;
}
- std::pair<int, std::string> Session::encryptWithRandom(RandomData random, std::string plainText)
+ std::pair<int, std::string> Session::encryptWithRandom([[maybe_unused]] RandomData random, std::string plainText)
{
assert(random.size() >= encryptRandomSize());
diff --git a/src/eventemitter/lagerstoreeventemitter.hpp b/src/eventemitter/lagerstoreeventemitter.hpp
--- a/src/eventemitter/lagerstoreeventemitter.hpp
+++ b/src/eventemitter/lagerstoreeventemitter.hpp
@@ -66,11 +66,11 @@
}
if (needsCleanup) {
- std::remove_if(m_listeners.begin(),
+ m_listeners.erase(std::remove_if(m_listeners.begin(),
m_listeners.end(),
[](auto ptr) {
return ptr.expired();
- });
+ }), m_listeners.end());
}
}
@@ -152,7 +152,7 @@
private:
void addListener(ListenerSP listener) {
- m_postingFunc([=]() {
+ m_postingFunc([=, this]() {
m_holder.m_listeners.push_back(listener);
});
}
diff --git a/src/job/cprjobhandler.cpp b/src/job/cprjobhandler.cpp
--- a/src/job/cprjobhandler.cpp
+++ b/src/job/cprjobhandler.cpp
@@ -129,7 +129,7 @@
void addToQueue(BaseJob job, Callback callback) {
boost::asio::post(
executor,
- [=] {
+ [=, this] {
// precondition: job has a queueId
auto queueId = job.queueId().value();
@@ -140,7 +140,7 @@
void clearQueue(std::string queueId) {
boost::asio::post(
executor,
- [=] { clearQueueImpl(queueId); });
+ [=, this] { clearQueueImpl(queueId); });
}
void clearQueueImpl(std::string queueId) {
@@ -156,7 +156,7 @@
kzo.job.dbg() << "this job is " << (status == Waiting ? "Waiting" : "Running") << std::endl;
if (status == Waiting) {
// Run callback in a different thread, just as in submitImpl().
- q->async([=] { callback(job.genResponse(fakeResponse)); } );
+ q->async([=, this] { callback(job.genResponse(fakeResponse)); } );
}
// if status is Running, the callback is already called
}
@@ -166,7 +166,7 @@
void popJob(std::string queueId) {
boost::asio::post(
executor,
- [=] { popJobImpl(queueId); });
+ [=, this] { popJobImpl(queueId); });
}
void popJobImpl(std::string queueId) {
@@ -178,7 +178,7 @@
void monitorQueues() {
boost::asio::post(
executor,
- [=] {
+ [=, this] {
// precondition: job has a queueId
for (auto &[queueId, queue] : jobQueues) { // need to change queue
if (! queue.empty()) {
@@ -187,7 +187,7 @@
queue.front().status = Running;
submitImpl(
job,
- [=](Response r) { // in new thread
+ [=, this](Response r) { // in new thread
callback(r);
if (! r.success() // should be enough for now
@@ -206,7 +206,7 @@
void addTimerToMap(TimerSP timer, std::optional<std::string> timerId) {
boost::asio::post(
executor,
- [=] {
+ [=, this] {
timerMap[timerId].push_back(timer);
});
}
@@ -214,15 +214,16 @@
void clearTimer(TimerSP timer, std::optional<std::string> timerId) {
boost::asio::post(
executor,
- [=] {
- std::remove(timerMap[timerId].begin(), timerMap[timerId].end(), timer);
+ [=, this] {
+ auto &timers = timerMap[timerId];
+ timers.erase(std::remove(timers.begin(), timers.end(), timer), timers.end());
});
}
void cancelAllTimers(std::optional<std::string> timerId) {
boost::asio::post(
executor,
- [=] {
+ [=, this] {
cancelAllTimersImpl(timerId);
});
}
@@ -244,7 +245,7 @@
auto dur = boost::asio::chrono::milliseconds(ms);
timer->expires_at(timer->expiry() + dur);
timer->async_wait(
- [=](const boost::system::error_code &error) {
+ [=, this](const boost::system::error_code &error) {
intervalTimerCallback(timer, func, ms, error);
});
}
@@ -287,7 +288,7 @@
: m_d(new Private{this, std::move(executor), Private::TimerMap{}, Private::JobMap{}})
{
setInterval(
- [=] {
+ [=, this] {
m_d->monitorQueues();
},
50, // ms
@@ -311,7 +312,7 @@
m_d->addTimerToMap(timer, timerId);
timer->async_wait(
- [=, timer=timer](const boost::system::error_code &error){
+ [=, this, timer=timer](const boost::system::error_code &error){
if (! error) {
func();
this->m_d->clearTimer(timer, timerId);
@@ -327,7 +328,7 @@
m_d->addTimerToMap(timer, timerId);
timer->async_wait(
- [=](const boost::system::error_code &error) {
+ [=, this](const boost::system::error_code &error) {
m_d->intervalTimerCallback(timer, func, ms, error);
});
}
@@ -459,7 +460,7 @@
std::shared_future<Response> res = std::visit(lager::visitor{
- [=](BaseJob::Get) {
+ [=, this](BaseJob::Get) {
if (readCallback) {
return cpr::GetCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
@@ -468,7 +469,7 @@
return cpr::GetCallback(callback, url, header, body, params);
}
},
- [=](BaseJob::Post) {
+ [=, this](BaseJob::Post) {
if (readCallback) {
return cpr::PostCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
@@ -477,7 +478,7 @@
return cpr::PostCallback(callback, url, header, body, params);
}
},
- [=](BaseJob::Put) {
+ [=, this](BaseJob::Put) {
if (readCallback) {
return cpr::PutCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
@@ -486,7 +487,7 @@
return cpr::PutCallback(callback, url, header, body, params);
}
},
- [=](BaseJob::Delete) {
+ [=, this](BaseJob::Delete) {
if (readCallback) {
return cpr::DeleteCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
@@ -497,7 +498,7 @@
}
}, method).share();
- q->async([=]() {
+ q->async([=, this]() {
userCallback(job.genResponse(res.get()));
});
}
@@ -506,7 +507,7 @@
{
boost::asio::post(
m_d->executor,
- [=] {
+ [=, this] {
auto ids = zug::into_vector(
zug::map([](auto i) { return i.first; }),
m_d->timerMap);
diff --git a/src/tests/crypto-test.cpp b/src/tests/crypto-test.cpp
--- a/src/tests/crypto-test.cpp
+++ b/src/tests/crypto-test.cpp
@@ -415,7 +415,10 @@
std::string encrypted;
// Can be moved from itself
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wself-move"
desc2 = std::move(desc2);
+#pragma GCC diagnostic pop
REQUIRE(desc2.valid());
std::tie(desc2, encrypted) = std::move(desc2).process(original);
diff --git a/src/tests/event-emitter-test.cpp b/src/tests/event-emitter-test.cpp
--- a/src/tests/event-emitter-test.cpp
+++ b/src/tests/event-emitter-test.cpp
@@ -80,4 +80,90 @@
REQUIRE(counter == 1);
REQUIRE(counter2 == 1);
}
+
+ SECTION("Expired listeners should be removed from internal list") {
+ auto guard = boost::asio::make_work_guard(ioContext.get_executor());
+ auto thread = std::thread([&] { ioContext.run(); });
+
+ std::vector<int> results;
+
+ {
+ auto w = ee.watchable();
+ w.after<ReceivingPresenceEvent>(
+ [&](auto) {
+ results.push_back(1);
+ });
+ }
+ // w is destroyed, listener expired
+
+ // Emit several events to trigger cleanup of expired listener
+ // via sendToListeners -> erase-remove
+ for (int i = 0; i < 5; ++i) {
+ ee.emit(ReceivingPresenceEvent{});
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds{100});
+
+ // Now register a new listener - if erase-remove works, the old
+ // expired listener was removed and no stale references remain
+ int newCounter = 0;
+ {
+ auto w2 = ee.watchable();
+ w2.after<ReceivingPresenceEvent>(
+ [&](auto) {
+ ++newCounter;
+ });
+
+ ee.emit(ReceivingPresenceEvent{});
+ ee.emit(ReceivingPresenceEvent{});
+ std::this_thread::sleep_for(std::chrono::milliseconds{100});
+ }
+
+ guard.reset();
+ thread.join();
+
+ REQUIRE(results.empty());
+ REQUIRE(newCounter == 2);
+ }
+
+ SECTION("Many expired listeners should be cleaned without issue") {
+ auto guard = boost::asio::make_work_guard(ioContext.get_executor());
+ auto thread = std::thread([&] { ioContext.run(); });
+
+ // Create and destroy many watchables to build up expired listeners
+ for (int i = 0; i < 50; ++i) {
+ auto w = ee.watchable();
+ std::atomic<int> unused{0};
+ w.after<ReceivingPresenceEvent>(
+ [&unused](auto) {
+ ++unused;
+ });
+ }
+ // All watchables destroyed, all listeners expired
+
+ // Emit events to trigger cleanup via erase-remove
+ for (int i = 0; i < 10; ++i) {
+ ee.emit(ReceivingPresenceEvent{});
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds{100});
+
+ // Verify emitter still works after cleanup
+ int aliveCounter = 0;
+ {
+ auto w = ee.watchable();
+ w.after<ReceivingPresenceEvent>(
+ [&](auto) {
+ ++aliveCounter;
+ });
+
+ ee.emit(ReceivingPresenceEvent{});
+ std::this_thread::sleep_for(std::chrono::milliseconds{100});
+ }
+
+ guard.reset();
+ thread.join();
+
+ REQUIRE(aliveCounter == 1);
+ }
}
diff --git a/src/tests/kazvjobtest.cpp b/src/tests/kazvjobtest.cpp
--- a/src/tests/kazvjobtest.cpp
+++ b/src/tests/kazvjobtest.cpp
@@ -77,7 +77,7 @@
std::string id{"testTimerId"};
h.setInterval(
- [&v, &h, id] {
+ [&v, &h, id]() {
v.push_back(50);
std::cout << "timer executed" << std::endl;
if (v.size() >= 2) {
@@ -86,7 +86,7 @@
}, 100, id);
h.setTimeout(
- [&h] {
+ [&h]() {
h.stop();
}, 300);
@@ -97,6 +97,67 @@
REQUIRE( v[1] == 50 );
}
+TEST_CASE("clearTimer should erase timer from map when timer fires", "[kazvjob]")
+{
+ boost::asio::io_context ioContext;
+ CprJobHandler h(ioContext.get_executor());
+
+ std::vector<int> firedWithIds;
+
+ // Set two timers with the same timerId - each fires once and
+ // is cleared via clearTimer -> erase-remove, then a new timer
+ // with the same ID is set
+ h.setTimeout(
+ [&firedWithIds, &h]() {
+ firedWithIds.push_back(1);
+ // Set a new timer with the same ID after this one fires
+ h.setTimeout(
+ [&firedWithIds, &h]() {
+ firedWithIds.push_back(2);
+ h.stop();
+ }, 50, "shared-id");
+ }, 50, "shared-id");
+
+ ioContext.run();
+
+ REQUIRE(firedWithIds.size() == 2);
+ REQUIRE(firedWithIds[0] == 1);
+ REQUIRE(firedWithIds[1] == 2);
+}
+
+TEST_CASE("Multiple timers with same id should be independently clearable", "[kazvjob]")
+{
+ boost::asio::io_context ioContext;
+ CprJobHandler h(ioContext.get_executor());
+
+ std::vector<int> fired;
+
+ // After each timer fires, clearTimer via erase-remove should
+ // remove only that specific timer, not affect others
+ h.setTimeout(
+ [&fired]() {
+ fired.push_back(1);
+ }, 50, "multi-id");
+
+ h.setTimeout(
+ [&fired]() {
+ fired.push_back(2);
+ }, 100, "multi-id");
+
+ h.setTimeout(
+ [&fired, &h]() {
+ fired.push_back(3);
+ h.stop();
+ }, 150, "multi-id");
+
+ ioContext.run();
+
+ REQUIRE(fired.size() == 3);
+ REQUIRE(fired[0] == 1);
+ REQUIRE(fired[1] == 2);
+ REQUIRE(fired[2] == 3);
+}
+
static BaseJob succJob =
BaseJob(TEST_SERVER_URL, "/.well-known/matrix/client", BaseJob::Get{}, "TestJob")
.withQueue("testjob");
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Jun 12, 8:57 PM (22 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1581848
Default Alt Text
D309.1781323073.diff (23 KB)
Attached To
Mode
D309: Fix compiler warnings
Attached
Detach File
Event Timeline
Log In to Comment