Skip to content

Commit 0322d67

Browse files
authored
fix: Ensure progress callback live long enough to be used (#205)
* fix: Init * fix: Rename var ref * Update context.test.cpp * fix: Test clean up to use new APIs and no warn * fix: Update docs * fix: Build and deprecations * fix: Clean up
1 parent 1ebc868 commit 0322d67

File tree

5 files changed

+388
-130
lines changed

5 files changed

+388
-130
lines changed

docs/context-settings.md

Lines changed: 65 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ See also:
1010

1111
## Quick start
1212

13-
The simplest way to configure the SDK is to create a `Context` with inline JSON and pass it to `Reader` or `Builder`:
13+
The simplest way to configure the SDK is to create a `Context` wrapped in a `shared_ptr` and pass it to `Reader` or `Builder`:
1414

1515
```cpp
1616
#include "c2pa.hpp"
1717

1818
// Create a Context with settings
19-
c2pa::Context context(R"({
19+
auto context = std::make_shared<c2pa::Context>(R"({
2020
"version": 1,
2121
"builder": {
2222
"claim_generator_info": {"name": "My App", "version": "1.0"},
@@ -35,7 +35,7 @@ c2pa::Builder builder(context, manifest_json);
3535
For default SDK configuration, just create an empty `Context`:
3636
3737
```cpp
38-
c2pa::Context context; // Uses SDK defaults
38+
auto context = std::make_shared<c2pa::Context>(); // Uses SDK defaults
3939
c2pa::Reader reader(context, "image.jpg");
4040
```
4141

@@ -66,18 +66,18 @@ c2pa::Reader reader(context, "image.jpg");
6666
### Context lifecycle
6767

6868
- **Non-copyable, moveable**: `Context` can be moved but not copied. After moving, `is_valid()` returns `false` on the source
69-
- **Used at construction**: `Reader` and `Builder` copy configuration from the context at construction time. The `Context` doesn't need to outlive them
70-
- **Reusable**: Use the same `Context` to create multiple readers and builders
69+
- **Pass as `shared_ptr`**: `Reader` and `Builder` retain a shared reference to the context, keeping it alive for their lifetime. This is required when using progress callbacks — without it, the callback can fire after the context is destroyed, causing a crash
70+
- **Reusable**: Use the same `shared_ptr<Context>` to create multiple readers and builders
7171

7272
```cpp
73-
c2pa::Context context(settings);
73+
auto context = std::make_shared<c2pa::Context>(settings);
7474

7575
// All three use the same configuration
7676
c2pa::Builder builder1(context, manifest1);
7777
c2pa::Builder builder2(context, manifest2);
7878
c2pa::Reader reader(context, "image.jpg");
7979

80-
// Context can go out of scope, readers/builders still work
80+
// context shared_ptr can go out of scope — reader/builder each hold a reference
8181
```
8282
8383
### Settings format
@@ -179,15 +179,15 @@ Use `ContextBuilder::with_progress_callback` to attach a callback before buildin
179179

180180
std::atomic<int> phase_count{0};
181181

182-
auto context = c2pa::Context::ContextBuilder()
182+
auto context = std::make_shared<c2pa::Context>(c2pa::Context::ContextBuilder()
183183
.with_progress_callback([&](c2pa::ProgressPhase phase, uint32_t step, uint32_t total) {
184184
++phase_count;
185185
// Return true to continue, false to cancel.
186186
return true;
187187
})
188-
.create_context();
188+
.create_context());
189189

190-
// Use the context normally — the callback fires automatically.
190+
// Pass as shared_ptr so the context stays alive while the callback can fire.
191191
c2pa::Builder builder(context, manifest_json);
192192
builder.sign("source.jpg", "output.jpg", signer);
193193
```
@@ -212,16 +212,16 @@ You may call `Context::cancel()` from another thread while the same `Context` re
212212
```cpp
213213
#include <thread>
214214

215-
auto context = c2pa::Context::ContextBuilder()
215+
auto context = std::make_shared<c2pa::Context>(c2pa::Context::ContextBuilder()
216216
.with_progress_callback([](c2pa::ProgressPhase, uint32_t, uint32_t) {
217217
return true; // Don't cancel from the callback — use cancel() instead.
218218
})
219-
.create_context();
219+
.create_context());
220220

221221
// Kick off a cancel after 500 ms from a background thread.
222-
std::thread cancel_thread([&context]() {
222+
std::thread cancel_thread([context]() {
223223
std::this_thread::sleep_for(std::chrono::milliseconds(500));
224-
context.cancel();
224+
context->cancel();
225225
});
226226

227227
try {
@@ -278,14 +278,14 @@ Reading → VerifyingManifest → VerifyingSignature → VerifyingAssetHash →
278278
`with_progress_callback` chains with other `ContextBuilder` methods:
279279

280280
```cpp
281-
auto context = c2pa::Context::ContextBuilder()
281+
auto context = std::make_shared<c2pa::Context>(c2pa::Context::ContextBuilder()
282282
.with_settings(settings)
283283
.with_signer(std::move(signer))
284284
.with_progress_callback([](c2pa::ProgressPhase phase, uint32_t step, uint32_t total) {
285285
// Update a UI progress bar, log phases, etc.
286286
return true;
287287
})
288-
.create_context();
288+
.create_context());
289289
```
290290
291291
## Common configuration patterns
@@ -370,11 +370,11 @@ c2pa::Builder prod_builder(prod_context, manifest);
370370

371371
### Temporary contexts
372372

373-
Since configuration is copied at construction, you can use temporary contexts:
373+
You can wrap a temporary `Context` in a `shared_ptr` inline:
374374

375375
```cpp
376376
c2pa::Reader reader(
377-
c2pa::Context(R"({"verify": {"remote_manifest_fetch": false}})"),
377+
std::make_shared<c2pa::Context>(R"({"verify": {"remote_manifest_fetch": false}})"),
378378
"image.jpg"
379379
);
380380
```
@@ -384,12 +384,12 @@ c2pa::Reader reader(
384384
`Reader` uses `Context` to control validation, trust configuration, network access, and performance.
385385

386386
> [!IMPORTANT]
387-
> `Context` is used only at construction. `Reader` copies the configuration it needs internally, so the `Context` doesn't need to outlive the `Reader`.
387+
> Pass `Context` as a `shared_ptr`. `Reader` retains a shared reference, keeping the context alive for its lifetime. This is required when using progress callbacks.
388388
389389
### Reading from a file
390390

391391
```cpp
392-
c2pa::Context context(R"({
392+
auto context = std::make_shared<c2pa::Context>(R"({
393393
"version": 1,
394394
"verify": {
395395
"remote_manifest_fetch": false,
@@ -414,10 +414,10 @@ std::cout << reader.json() << std::endl;
414414
`Builder` uses `Context` to control manifest creation, signing, thumbnails, and more.
415415

416416
> [!IMPORTANT]
417-
> The `Context` is used only when constructing the `Builder`. It copies the configuration internally, so the `Context` doesn't need to outlive the `Builder`.
417+
> Pass `Context` as a `shared_ptr`. `Builder` retains a shared reference, keeping the context alive for its lifetime. This is required when using progress callbacks.
418418
419419
```cpp
420-
c2pa::Context context(R"({
420+
auto context = std::make_shared<c2pa::Context>(R"({
421421
"version": 1,
422422
"builder": {
423423
"claim_generator_info": {"name": "My App", "version": "1.0"},
@@ -515,7 +515,7 @@ MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ
515515
...
516516
-----END CERTIFICATE-----)";
517517

518-
c2pa::Context context(R"({
518+
auto context = std::make_shared<c2pa::Context>(R"({
519519
"version": 1,
520520
"trust": {
521521
"user_anchors": ")" + test_root_ca + R"("
@@ -540,7 +540,7 @@ settings.update(R"({
540540
}
541541
})");
542542
543-
c2pa::Context context(settings);
543+
auto context = std::make_shared<c2pa::Context>(settings);
544544
c2pa::Reader reader(context, "signed_asset.jpg");
545545
```
546546

@@ -567,9 +567,9 @@ For the PEM string (for example in `user_anchors` in above example):
567567
Load in your application:
568568

569569
```cpp
570-
auto context = c2pa::Context::ContextBuilder()
570+
auto context = std::make_shared<c2pa::Context>(c2pa::Context::ContextBuilder()
571571
.with_json_settings_file("dev_trust_config.json")
572-
.create_context();
572+
.create_context());
573573

574574
c2pa::Reader reader(context, "signed_asset.jpg");
575575
```
@@ -624,7 +624,7 @@ The following table lists the key properties (all default to `true`):
624624
Disable network-dependent features:
625625
626626
```cpp
627-
c2pa::Context context(R"({
627+
auto context = std::make_shared<c2pa::Context>(R"({
628628
"version": 1,
629629
"verify": {
630630
"remote_manifest_fetch": false,
@@ -659,7 +659,7 @@ c2pa::Context dev_context(dev_settings);
659659
Enable all validation features for certification or compliance testing:
660660

661661
```cpp
662-
c2pa::Context context(R"({
662+
auto context = std::make_shared<c2pa::Context>(R"({
663663
"version": 1,
664664
"verify": {
665665
"strict_v1_validation": true,
@@ -733,7 +733,7 @@ See [ClaimGeneratorInfoSettings in the SDK object reference](https://opensource.
733733
**Example:**
734734
735735
```cpp
736-
c2pa::Context context(R"({
736+
auto context = std::make_shared<c2pa::Context>(R"({
737737
"version": 1,
738738
"builder": {
739739
"claim_generator_info": {
@@ -809,7 +809,7 @@ std::string config = R"({
809809
}
810810
})";
811811
812-
c2pa::Context context(config);
812+
auto context = std::make_shared<c2pa::Context>(config);
813813
c2pa::Builder builder(context, manifest_json);
814814
builder.sign(source_path, dest_path); // Uses signer from context
815815
```
@@ -886,9 +886,11 @@ The SDK introduced Context-based APIs to replace constructors and functions that
886886
| Deprecated API | Replacement |
887887
|---|---|
888888
| `load_settings(data, format)` | [`Context` constructors or `ContextBuilder`](#replacing-load_settings) |
889-
| `Reader(format, stream)` | [`Reader(context, format, stream)`](#adding-a-context-parameter-to-reader-and-builder) |
890-
| `Reader(source_path)` | [`Reader(context, source_path)`](#adding-a-context-parameter-to-reader-and-builder) |
891-
| `Builder(manifest_json)` | [`Builder(context, manifest_json)`](#adding-a-context-parameter-to-reader-and-builder) |
889+
| `Reader(format, stream)` | [`Reader(shared_ptr<IContextProvider>, format, stream)`](#adding-a-context-parameter-to-reader-and-builder) |
890+
| `Reader(source_path)` | [`Reader(shared_ptr<IContextProvider>, source_path)`](#adding-a-context-parameter-to-reader-and-builder) |
891+
| `Builder(manifest_json)` | [`Builder(shared_ptr<IContextProvider>, manifest_json)`](#adding-a-context-parameter-to-reader-and-builder) |
892+
| `Reader(IContextProvider&, ...)` | [`Reader(shared_ptr<IContextProvider>, ...)`](#using-shared_ptr-instead-of-reference-for-reader-and-builder) |
893+
| `Builder(IContextProvider&, ...)` | [`Builder(shared_ptr<IContextProvider>, ...)`](#using-shared_ptr-instead-of-reference-for-reader-and-builder) |
892894
| `Builder::sign(..., ostream, ...)` | [`Builder::sign(..., iostream, ...)`](#using-iostream-instead-of-ostream-in-buildersign) |
893895
894896
### Replacing load_settings
@@ -927,7 +929,7 @@ The following constructors are deprecated because they rely on thread-local sett
927929
- `Reader(const std::filesystem::path& source_path)`
928930
- `Builder(const std::string& manifest_json)`
929931
930-
The migration path for each is to create a `Context` and pass it as the first argument.
932+
The migration path is to create a `shared_ptr<Context>` and pass it as the first argument.
931933
932934
**Deprecated:**
933935
@@ -937,24 +939,44 @@ c2pa::Reader reader("image.jpg");
937939
c2pa::Builder builder(manifest_json);
938940
```
939941

940-
**With context API:**
942+
**With shared_ptr context API:**
941943

942944
```cpp
943-
c2pa::Context context; // or Context(settings) or Context(json_string)
945+
auto context = std::make_shared<c2pa::Context>(); // or Context(settings) or Context(json)
944946
c2pa::Reader reader(context, "image/jpeg", stream);
945947
c2pa::Reader reader(context, "image.jpg");
946948
c2pa::Builder builder(context, manifest_json);
947949
```
948950
949-
If you need default SDK behavior and have no custom settings, `c2pa::Context context;` with no arguments is sufficient.
951+
### Using shared_ptr instead of reference for Reader and Builder
950952
951-
#### About IContextProvider
953+
The `IContextProvider&` reference overloads are deprecated because they do not extend the lifetime of the context. If the context is destroyed while a `Reader` or `Builder` has a progress callback registered, the callback fires against freed memory, causing a crash.
952954
953-
The deprecation warnings reference `IContextProvider` in their suggested fix (e.g., "Use Reader(IContextProvider& context, ...)"). `IContextProvider` is the interface that `Reader` and `Builder` constructors accept. `Context` is the SDK's built-in implementation of this interface.
955+
**Deprecated:**
954956
955-
When the deprecation warning says "Use Reader(IContextProvider& context, ...)", passing a `Context` object satisfies that parameter.
957+
```cpp
958+
c2pa::Context context;
959+
c2pa::Reader reader(context, "image.jpg"); // reference — context not kept alive
960+
c2pa::Builder builder(context, manifest_json); // reference — context not kept alive
961+
c2pa::Reader::from_asset(context, "image.jpg"); // reference — context not kept alive
962+
```
956963

957-
External libraries can also implement `IContextProvider` to provide their own context objects (for example, wrapping a platform-specific configuration system). The interface is minimal: any class that can produce a valid `C2paContext*` pointer and report its validity can serve as a context provider. This becomes relevant when building integrations that need to manage context lifetime or initialization differently than the SDK's `Context` class does.
964+
**With shared_ptr:**
965+
966+
```cpp
967+
auto context = std::make_shared<c2pa::Context>();
968+
c2pa::Reader reader(context, "image.jpg");
969+
c2pa::Builder builder(context, manifest_json);
970+
c2pa::Reader::from_asset(context, "image.jpg");
971+
```
972+
973+
The `shared_ptr` overloads accept any `shared_ptr<IContextProvider>`, so custom `IContextProvider` implementations work the same way, wrap them in a `shared_ptr` before passing.
974+
975+
#### About IContextProvider
976+
977+
`IContextProvider` is the interface that `Reader` and `Builder` constructors accept. `Context` is the SDK's built-in implementation. The deprecation warnings reference it in the suggested replacement (e.g., `"Use Reader(std::shared_ptr<IContextProvider>, ...)`").
978+
979+
External libraries can implement `IContextProvider` to supply their own context objects. The interface requires a valid `C2paContext*` pointer and an `is_valid()` check. Wrap your implementation in a `shared_ptr` when passing to `Reader` or `Builder`.
958980
959981
### Builder::sign overloads
960982
@@ -982,10 +1004,10 @@ If a signer is configured in the `Context` (through settings JSON or `ContextBui
9821004
```cpp
9831005
c2pa::Signer signer("es256", certs, key, tsa_url);
9841006

985-
auto context = c2pa::Context::ContextBuilder()
1007+
auto context = std::make_shared<c2pa::Context>(c2pa::Context::ContextBuilder()
9861008
.with_json(settings_json)
9871009
.with_signer(std::move(signer)) // signer is consumed here
988-
.create_context();
1010+
.create_context());
9891011

9901012
c2pa::Builder builder(context, manifest_json);
9911013

0 commit comments

Comments
 (0)