Skip to content

Commit af6d709

Browse files
UnbreakableMJAntigravity (Gemini 3.5 Flash)
andcommitted
feat(skills): improve spacecraft-cpp-guidelines with Core Guidelines
Enhance the C++ language-guidelines skill by integrating key security checks from the C++ Core Guidelines. Specifically adds rules and skeletons for named lock guards (avoiding immediate destruction races), deadlock-free multi-mutex locking via std::scoped_lock, condition variable predicate checks to prevent spurious wakeups, Rule of Zero & Rule of Five resource management lifecycles, and type-safe scoped enum classes / std::array parameters. Includes recompiled zip and skill packages. Co-Authored-By: Antigravity (Gemini 3.5 Flash) <noreply@google.com>
1 parent 6de41e2 commit af6d709

4 files changed

Lines changed: 183 additions & 33 deletions

File tree

spacecraft-cpp-guidelines.skill

2.43 KB
Binary file not shown.

spacecraft-cpp-guidelines.zip

2.43 KB
Binary file not shown.

spacecraft-cpp-guidelines/SKILL.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ website: https://Construct.SpacecraftSoftware.org/
1818
- **Stability and Safety first (Standard §3 Priority 1).** C++ historically lacks memory safety. Enforce strict compile-time types using Safe C++ extensions (borrow checker, safe contexts) where available, compile with extensive compiler hardening modes in standard environments, or build with the Fil-C drop-in compiler.
1919
- **Then Performance (Priority 2).** Do not sacrifice low-latency execution patterns. Utilize zero-overhead compiler hardening options that trap out-of-bounds access with minimal CPU cost.
2020
- **Modern Concurrency.** Prevent thread leaks and raw thread crashes by using `std::jthread` (C++20+) which provides cooperative thread cancellation tokens and auto-joining destructors.
21-
- **Deterministic Lifetime Management.** Enforce RAII (Resource Acquisition Is Initialization). Keep allocations bounded, ban raw pointers (`new`/`delete`), and manage resources using smart pointers (`std::unique_ptr`, `std::shared_ptr`).
21+
- **Deterministic Lifetime Management.** Enforce RAII (Resource Acquisition Is Initialization). Keep allocations bounded, ban raw pointers (`new`/`delete`), and manage resources using smart pointers (`std::unique_ptr`, `std::shared_ptr`). Apply the Rule of Zero (compiler-generated defaults) or Rule of Five (fully handle all copy/move/destructor lifecycle members if one is custom defined).
2222

2323
## Memory Safety & Hardening Mode
2424
- **Safe C++ (safecpp.org) Syntax:** When using Safe C++ compiler extensions (Circle), structure safety-critical paths inside explicit `safe` blocks. Inside `safe {}` contexts, the borrow checker restricts pointer arithmetic, dynamic casting, and raw references, enforcing Rust-like static mutability and lifetime verification.
2525
- **Compiler Hardening Flags:** In standard C++ toolchains, compiler hardening mode configurations must be active. Trapping bounds errors at runtime is mandatory:
2626
- **GCC:** Configure `-D_GLIBCXX_ASSERTIONS` and `-fhardened` to trap out-of-bound vector/span access.
2727
- **Clang:** Configure `-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE` to enable extensive runtime bounds and precondition assertions.
2828
- **Fil-C Drop-in Safety:** When targeting legacy C++ components, compile code with the Fil-C compiler toolchain fork. Fil-C utilizes garbage collection and InvisiCaps capabilities to trap spatial and temporal access violations, panic-aborting instead of permitting undefined behavior.
29-
- **Smart Resource Management:** Raw pointer management is prohibited. All dynamic heap allocations must be wrapped in `std::unique_ptr` or `std::shared_ptr`. Avoid `std::shared_ptr` cyclic dependencies.
29+
- **Smart Resource Management:** Raw pointer management is prohibited. All dynamic heap allocations must be wrapped in `std::unique_ptr` or `std::shared_ptr`. Ownership must never be transferred via raw pointers or references. Prefer `std::array` or `std::vector` over C-style raw arrays, and scoped `enum class` over plain `enum` to prevent implicit casting and namespace pollution.
3030

3131
## Concurrency vs. Performance Tradeoffs
3232
- **When Concurrency Helps (Do Thread Safety):**
@@ -37,6 +37,9 @@ website: https://Construct.SpacecraftSoftware.org/
3737
- **Dynamic Shared Mutation:** Modifying standard containers (e.g., `std::vector`, `std::unordered_map`) across threads without explicit synchronization.
3838
- **Thread Leakage / Crashes:** Spawning raw `std::thread` elements without joining or detaching before destruction, raising `std::terminate()` crashes.
3939
- **Contended Mutex Locking:** Performing long-lived blocking mutex locks inside high-frequency real-time loops. Use lock-free structures or fine-grained locks.
40+
- **Unnamed Lock Guards:** Never declare an unnamed lock guard or unique lock (e.g., `std::lock_guard<std::mutex>(mtx);` is a temporary object that destroys immediately, releasing the mutex instantly and exposing the critical section to data races).
41+
- **Deadlock Multi-mutex Locking:** Acquiring multiple mutexes individually in different orders. Use `std::scoped_lock` to acquire multiple mutexes atomically and deadlock-free. Never call unknown callbacks or code while holding a lock.
42+
- **Condition Spurious Wakeups:** Waiting on condition variables without a predicate check, resulting in spurious wakeups and out-of-order execution.
4043

4144
## Mandatory Abstraction Choice
4245
Always choose the safety abstraction corresponding to the compiling environment:
@@ -50,8 +53,10 @@ Always choose the safety abstraction corresponding to the compiling environment:
5053
1. **jthread over raw thread:** Replace all instances of `std::thread` with `std::jthread`. jthreads auto-join upon exiting their scope and carry interruption tokens.
5154
2. **Hardened CMake Configuration:** CMake build files must explicitly append GCC/Clang hardening mode compiler flags for compile steps.
5255
3. **C++26 Span Boundaries:** Use `std::span` to wrap arrays and raw pointers. Hardened runtime flags ensure subscript index queries are trapped upon out-of-bounds attempts.
53-
4. **RAII Lock Management:** Use `std::lock_guard` or `std::unique_lock` to manage mutex critical sections. Never call `.lock()` and `.unlock()` manually.
56+
4. **RAII Lock Management:** Use `std::lock_guard` or `std::unique_lock` to manage mutex critical sections, and ensure every lock guard is named. Use `std::scoped_lock` for acquiring multiple mutexes.
5457
5. **Warnings-as-Errors:** Configure `-Werror` and strict diagnostic flags (`-Wall -Wextra -Wpedantic`) to prevent compilation warnings.
58+
6. **Rule of Zero/Five:** Enforce the Rule of Zero for resource-free classes, or Rule of Five when manual resource tracking is required.
59+
7. **Condition variable predicates:** Always invoke condition variable waits using the predicate overload: `cv.wait(lock, [] { return condition; });`.
5560

5661
## Build, Tooling & CI (Non-Negotiable)
5762
- **Toolchain floor:** C++20 compliant compiler, CMake ≥ 3.20.
@@ -60,21 +65,32 @@ Always choose the safety abstraction corresponding to the compiling environment:
6065
- **Testing:** Unit test targets written using GoogleTest or Catch2.
6166

6267
## Anti-Patterns (Never Do These)
63-
- Allocating memory using `new` or deallocating using `delete` (use `std::make_unique`).
68+
- Allocating memory using `new` or deallocating using `delete` (use `std::make_unique` or `std::make_shared`).
6469
- Spawning raw `std::thread` instances.
6570
- Performing pointer arithmetic on raw pointers (use `std::span` or `std::array`).
66-
- Blocking threads manually using busy-wait loops (use condition variables or atomic waits).
71+
- Blocking threads manually using busy-wait loops.
6772
- Locking mutexes without using RAII lock guards.
73+
- Declaring unnamed lock guards (e.g. `std::lock_guard<std::mutex>(mtx);`).
74+
- Calling foreign callbacks or unknown code while holding a mutex.
75+
- Invoking `cv.wait(lock)` without a condition predicate.
76+
- Violating the Rule of Zero / Five (e.g., custom destructor without custom copy/move constructors or assignment operators).
77+
- Using C-style raw arrays (use `std::array` or `std::vector`).
78+
- Declaring plain enums or macro constants (use scoped `enum class` and `constexpr` variables).
6879
- Swallowing compilation warnings via compiler ignore macros.
6980

7081
## Pre-Commit Checklist (Verify Every Time)
7182
- [ ] No raw `new`/`delete` or unchecked pointer arithmetic remains in production paths
7283
- [ ] Raw `std::thread` usage replaced with `std::jthread`
7384
- [ ] CMakeLists.txt configures Clang extensive or GCC hardened compilation flags
74-
- [ ] Mutex locking paths manage resource allocation using `std::lock_guard`
85+
- [ ] Mutex locking paths manage resource allocation using *named* `std::lock_guard` or `std::unique_lock` instances
86+
- [ ] Multi-mutex locking is handled atomically using `std::scoped_lock`
87+
- [ ] Condition variable waits include an explicit predicate lambda check: `cv.wait(lock, [] { ... })`
88+
- [ ] Non-owning pointers do not transfer resource ownership (ownership managed by `std::unique_ptr`/`std::shared_ptr`)
89+
- [ ] All custom resource lifecycle classes conform to the Rule of Zero or Rule of Five
7590
- [ ] Array references are passed using bounds-checked `std::span` or `std::array` wrappers
91+
- [ ] Enums are scoped `enum class` declarations
7692
- [ ] Safe C++ code uses `safe` contexts and `std2` library equivalents where available
77-
- [ ] compiler diagnostics compile clean under `-Werror -Wall -Wextra -Wpedantic`
93+
- [ ] Compiler diagnostics compile clean under `-Werror -Wall -Wextra -Wpedantic`
7894
- [ ] GoogleTest or Catch2 tests execute and pass successfully
7995
- [ ] Clang-Tidy reports zero static analysis errors
8096

spacecraft-cpp-guidelines/references/Spacecraft_Cpp_Guidelines.md

Lines changed: 160 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,23 @@ add_executable(telemetry_node main.cpp telemetry_worker.cpp)
9191

9292
---
9393

94-
## 3. Concurrency: Safe Thread Management via `std::jthread`
94+
## 3. Concurrency: Safe Thread Management, Named Locks, scoped_lock, and Condition Predicates
9595

96-
Avoid raw `std::thread` to prevent crashes due to unjoined threads. Use C++20 `std::jthread` to automatically join upon scope exit and handle cooperative thread cancellation.
96+
Avoid raw `std::thread` to prevent crashes due to unjoined threads. Use C++20 `std::jthread` to automatically join upon scope exit, and always name your lock guards to prevent immediate release vulnerabilities.
9797

9898
```cpp
9999
#include <iostream>
100100
#include <vector>
101101
#include <thread>
102102
#include <mutex>
103+
#include <condition_variable>
103104
#include <chrono>
104105
#include <atomic>
105106

106107
class TelemetryWorker {
107108
private:
108109
std::mutex mtx_;
110+
std::condition_variable cv_;
109111
std::vector<double> readings_;
110112
std::atomic<bool> status_active_{false};
111113
std::jthread worker_thread_;
@@ -118,18 +120,21 @@ private:
118120
// Simulating sensor poll
119121
double mock_reading = 42.0;
120122

121-
// Lock context safely using RAII guard
123+
// Lock context safely using a NAMED RAII guard
122124
{
125+
// CRITICAL: Always name the lock variable (here: 'lock').
126+
// DO NOT write: std::lock_guard<std::mutex>(mtx_); which creates a temporary that is immediately destroyed!
123127
std::lock_guard<std::mutex> lock(mtx_);
124128
readings_.push_back(mock_reading);
125129
}
130+
cv_.notify_one();
126131
}
127132
}
128133

129134
public:
130135
TelemetryWorker() = default;
131136

132-
// Non-copyable
137+
// Non-copyable (Rule of Five)
133138
TelemetryWorker(const TelemetryWorker&) = delete;
134139
TelemetryWorker& operator=(const TelemetryWorker&) = delete;
135140

@@ -139,26 +144,150 @@ public:
139144

140145
void start() {
141146
status_active_.store(true);
142-
// jthread takes a function accepting a std::stop_token automatically
143147
worker_thread_ = std::jthread(&TelemetryWorker::run_loop, this);
144148
}
145149

146150
void stop() {
147151
status_active_.store(false);
148-
// Request thread interruption cooperatively
149152
worker_thread_.request_stop();
150153
}
151154

152-
std::vector<double> get_readings() {
153-
std::lock_guard<std::mutex> lock(mtx_);
154-
return readings_;
155+
// Safely await new reading using a condition variable predicate loop
156+
double await_next_reading() {
157+
std::unique_lock<std::mutex> lock(mtx_);
158+
159+
// CRITICAL: Always pass a predicate lambda to cv.wait to prevent spurious wakeup bugs
160+
cv_.wait(lock, [this] { return !readings_.empty(); });
161+
162+
double val = readings_.back();
163+
readings_.pop_back();
164+
return val;
155165
}
156166
};
167+
168+
// Deadlock-free Multi-Mutex Locking
169+
struct Account {
170+
std::mutex mtx_;
171+
double balance_ = 0.0;
172+
};
173+
174+
void transfer_funds(Account& from, Account& to, double amount) {
175+
// CRITICAL: Use std::scoped_lock to acquire multiple mutexes atomically and deadlock-free
176+
std::scoped_lock lock(from.mtx_, to.mtx_);
177+
178+
from.balance_ -= amount;
179+
to.balance_ += amount;
180+
}
157181
```
158182
159183
---
160184
161-
## 4. Drop-In Memory Safety: Fil-C Compiler Setup
185+
## 4. Lifecycle Management: Rule of Zero & Rule of Five
186+
187+
If a class does not manage resources directly, rely on the compiler-generated defaults (**Rule of Zero**). If a class manages resources manually, you must explicitly declare or delete all five special member functions (**Rule of Five**).
188+
189+
```cpp
190+
#include <cstddef>
191+
#include <memory>
192+
#include <algorithm>
193+
#include <stdexcept>
194+
195+
class SafeBufferManager {
196+
private:
197+
std::unique_ptr<char[]> buffer_;
198+
std::size_t size_;
199+
200+
public:
201+
explicit SafeBufferManager(std::size_t size)
202+
: buffer_(std::make_unique<char[]>(size)), size_(size) {}
203+
204+
// --- RULE OF FIVE IMPLEMENTATION ---
205+
206+
// 1. Destructor
207+
~SafeBufferManager() = default; // unique_ptr handles cleanup automatically
208+
209+
// 2. Copy Constructor (Deep copy allocation)
210+
SafeBufferManager(const SafeBufferManager& other)
211+
: buffer_(std::make_unique<char[]>(other.size_)), size_(other.size_) {
212+
std::copy_n(other.buffer_.get(), size_, buffer_.get());
213+
}
214+
215+
// 3. Copy Assignment Operator (Strong exception safety)
216+
SafeBufferManager& operator=(const SafeBufferManager& other) {
217+
if (this != &other) {
218+
auto temp_buffer = std::make_unique<char[]>(other.size_);
219+
std::copy_n(other.buffer_.get(), other.size_, temp_buffer.get());
220+
221+
buffer_ = std::move(temp_buffer);
222+
size_ = other.size_;
223+
}
224+
return *this;
225+
}
226+
227+
// 4. Move Constructor
228+
SafeBufferManager(SafeBufferManager&& other) noexcept
229+
: buffer_(std::move(other.buffer_)), size_(other.size_) {
230+
other.size_ = 0;
231+
}
232+
233+
// 5. Move Assignment Operator
234+
SafeBufferManager& operator=(SafeBufferManager&& other) noexcept {
235+
if (this != &other) {
236+
buffer_ = std::move(other.buffer_);
237+
size_ = other.size_;
238+
other.size_ = 0;
239+
}
240+
return *this;
241+
}
242+
243+
// Safe accessor
244+
char& at(std::size_t index) {
245+
if (index >= size_) {
246+
throw std::out_of_range("Buffer bounds overflow");
247+
}
248+
return buffer_[index];
249+
}
250+
};
251+
```
252+
253+
---
254+
255+
## 5. Type-Safe Interfaces: Scoped Enums, std::array, and Ownership
256+
257+
Do not use legacy C-style features. Enforce type safety at the API boundary.
258+
259+
```cpp
260+
#include <array>
261+
#include <string_view>
262+
#include <iostream>
263+
264+
// CRITICAL: Prefer scoped enum class to prevent global scope contamination and implicit casting
265+
enum class TelemetryChannel {
266+
velocity,
267+
altitude,
268+
temperature,
269+
pressure
270+
};
271+
272+
// CRITICAL: Prefer std::array over raw C arrays to maintain bounds safety information
273+
struct SensorCalibration {
274+
TelemetryChannel channel;
275+
std::array<double, 4> coefficients; // Safe bounds
276+
};
277+
278+
// Use std::string_view for zero-overhead, read-only string references
279+
void log_calibration(std::string_view sensor_name, const SensorCalibration& cal) {
280+
std::cout << "Sensor: " << sensor_name << ", coeffs: ";
281+
for (double val : cal.coefficients) {
282+
std::cout << val << " ";
283+
}
284+
std::cout << "\n";
285+
}
286+
```
287+
288+
---
289+
290+
## 6. Drop-In Memory Safety: Fil-C Compiler Setup
162291
163292
To run legacy C++ code safely, use the **Fil-C** compiler toolchain. Fil-C replaces raw pointers with capabilities (InvisiCaps) and implements a concurrent garbage collector to block all spatial and temporal bugs.
164293
@@ -177,9 +306,9 @@ $CXX -O3 -std=c++20 main.cpp -o telemetry_node_safe
177306

178307
---
179308

180-
## 5. Testing: GoogleTest Assertions
309+
## 7. Testing: GoogleTest Assertions
181310

182-
Test concurrent code and bounds assertions using GoogleTest.
311+
Test concurrent code, condition variables, and bounds assertions using GoogleTest.
183312

184313
```cpp
185314
#include <gtest/gtest.h>
@@ -192,36 +321,41 @@ TEST(TelemetryWorkerTest, CaptureReadingsSuccessfully) {
192321
std::this_thread::sleep_for(std::chrono::milliseconds(250));
193322
worker.stop();
194323

195-
auto readings = worker.get_readings();
196-
197-
// Verify that data was appended by background jthread
198-
EXPECT_FALSE(readings.empty());
199-
for (double val : readings) {
200-
EXPECT_DOUBLE_EQ(val, 42.0);
201-
}
324+
double latest_value = worker.await_next_reading();
325+
EXPECT_DOUBLE_EQ(latest_value, 42.0);
202326
}
203327
```
204328

205329
---
206330

207-
## 6. Common Pitfalls & Troubleshooting
331+
## 8. Common Pitfalls & Troubleshooting
208332

209333
| Pitfall | Symptom | Corrective Action |
210334
| :--- | :--- | :--- |
211335
| **Raw Pointer allocation** | Use-after-free, memory leaks | Use `std::make_unique` or `std::make_shared`. |
212336
| **Raw `std::thread` destruction** | `std::terminate()` aborts runtime | Replace thread with C++20 `std::jthread`. |
213337
| **Unprotected shared container** | Data races, memory corruptions | Synchronize accesses using `std::mutex` and `std::lock_guard`. |
338+
| **Unnamed Lock Guards** | Data races on protected resources | Always name the lock variable: `std::lock_guard<std::mutex> lock(mtx);`. |
339+
| **Spurious Wakeups** | Program proceeds with invalid data | Always wait on condition variables using a predicate lambda. |
340+
| **Rule of Five Violation** | Double free, slice copies, memory leaks | If writing a destructor or copy/move operation, implement all 5. |
341+
| **C-Style raw arrays** | Buffer overflows, decay to pointer | Use `std::array` or `std::vector` combined with hardening. |
342+
| **Plain enums or macros** | Namespace clashes, implicit cast bugs | Use scoped `enum class` and compile-time `constexpr` values. |
214343
| **Out-of-bounds indexing** | Security exploits, silent bugs | Enable `-fhardened` / `_LIBCPP_HARDENING_MODE_EXTENSIVE`. |
215-
| **Recursive mutex locking** | Thread deadlocks | Redesign lock domains; avoid calling locked functions from within locks. |
344+
| **Recursive mutex locking** | Thread deadlocks | Redesign lock domains; use `std::scoped_lock` for multi-locking. |
216345

217346
---
218347

219-
## 7. Code Review Compliance Gate
348+
## 9. Code Review Compliance Gate
220349

221350
Before merging C++ code, verify:
222351
1. No raw `new`/`delete` operators or manual pointer arithmetic exists in production files.
223352
2. Background threading utilizes `std::jthread` instead of raw `std::thread`.
224-
3. Mutex locks are managed exclusively using RAII class constructs (`std::lock_guard`).
225-
4. GCC/Clang extensive hardening flags are configured in CMakeLists.txt.
226-
5. Safe C++ blocks utilize `safe` keywords and `std2` library classes.
227-
6. Compilations pass cleanly without diagnostic warnings under `-Werror`.
353+
3. Mutex locks are managed exclusively using *named* RAII class constructs.
354+
4. Multi-mutex acquisition is handled atomically using `std::scoped_lock`.
355+
5. All condition variable wait loops include a predicate checking lambda to prevent spurious wakeups.
356+
6. Custom resource managers conform fully to the Rule of Zero or Rule of Five guidelines.
357+
7. C-style arrays are banned in favor of `std::array` or `std::vector`.
358+
8. Enums are declared as scoped `enum class` to prevent implicit casts.
359+
9. GCC/Clang extensive hardening flags are configured in CMakeLists.txt.
360+
10. Safe C++ blocks utilize `safe` keywords and `std2` library classes.
361+
11. Compilations pass cleanly without diagnostic warnings under `-Werror`.

0 commit comments

Comments
 (0)