Skip to content

Commit d09d822

Browse files
fix(chapel-binding): real-Chapel-2.x typecheck + WIRED-subset zig build fix (#141)
## Summary Follow-up fix for the chapel binding-tier-1 pilot landed in #135 — its first CI run on the Chapel CI workflow failed for three real reasons, all of which were hiding behind the binding's never-having-been-compiled state. Locally validated end-to-end with `chpl 2.8.0` + `zig 0.15.2` against a freshly-built libproven.so: ``` chapel-symbol-audit: PASS 5 / FAIL 0 hello_smoke: OK (libproven 0.1.0) TestFfiContract: OK TestLibraryInfo: OK (version=0.1.0) TestSafeHeader: OK TestSafePath: OK ``` ## Three root causes ### 1. `T?` syntax is class-only — every Safe*.chpl module was broken The pre-existing binding used Chapel's `T?` postfix on value-type returns (`bool?`, `int(64)?`, `string?`). That syntax is class-only in Chapel 2.x — every Safe*.chpl module failed to typecheck. This is the deep finding behind the brief's "well-written code, never linked" diagnosis: the binding had never been compiled, full stop. **Fix:** added a `Maybe(T)` generic record to `LibProven.chpl` with constructors `some(v)` and `absent(T)`. (`absent` rather than `none`/`nothing` because both of those are Chapel reserved words.) Rewrote every Safe*.chpl wrapper + smoke + 3 tests to use it. `Proven.chpl` adds `public import SafePath/SafeHeader/LibProven` so callers can write `SafePath.hasTraversal(...)` as a qualified reference (`public use` alone does not bring the module name in scope as a qualifier). `enum ProvenStatus : int(32) { ... }` typed-enum syntax was also removed in Chapel 2.x; dropped the type spec. `extractString` / `cStringToChapel` switched to `try!` for the now-throwing `string.createCopyingBuffer`. ### 2. `ffi/zig/build.zig` depends on a missing repo `hyperpolymath/idris2-zig-ffi` returns 404 to authenticated `gh`. The same break blocks `zig-ffi.yml` on main; resolving the upstream dep is out of scope for the Chapel pilot. All exports in `ffi/zig/src/main.zig` are pure-Zig (only `@import (\"std\")` + `@import(\"builtin\")`); none consume the `idris2_zig_ffi` module. Switched `chapel-build` to compile `main.zig` directly via `zig build-lib -dynamic -lc`, producing a libproven.so with the WIRED 5 symbols. `build.zig.zon` declares `minimum_zig_version = \"0.15.2\"`; bumped the CI's pinned Zig from 0.13.0 → 0.15.2 to match. ### 3. `proven.h` missing `proven_version` / `proven_build_info` `chpl` performs extern-decl resolution against included C headers and errors when an `extern \"proven_X\"` references a symbol not declared in any `-I`-supplied header. `proven_version` and `proven_build_info` ARE exported by `ffi/zig/src/main.zig` (always have been) but were absent from `proven.h`. Added two declarations to `bindings/c/include/proven.h`. These do NOT introduce new Zig exports — they document functions `main.zig` has been shipping all along. ## Diff scope 18 files changed: chapel-ci.yml, proven.h, README.adoc, every `bindings/chapel/src/*.chpl`, smoke + 3 tests. Detachability is preserved — every change is inside the detachable file set (`bindings/chapel/**`, `bindings/c/include/proven.h`, the workflow). ## Test plan - [ ] CI: `detachability-guard` GREEN - [ ] CI: `chapel-build` GREEN — Zig 0.15.2 + `zig build-lib -lc` builds libproven.so containing WIRED 5 - [ ] CI: `chapel-symbol-audit` PASS 5 / FAIL 0 - [ ] CI: `chapel-smoke` exit-0 - [ ] CI: `chapel-tests` — all 4 tests GREEN - [ ] CI: `chapel-detachable-build` builds + tests from sparse checkout - [ ] Auto-merge armed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 40b127f commit d09d822

18 files changed

Lines changed: 370 additions & 409 deletions

.github/workflows/chapel-ci.yml

Lines changed: 89 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,19 @@ concurrency:
5454
env:
5555
# Pinned Chapel deb (Ubuntu 22.04 amd64). SHA-256 verified out-of-band
5656
# against the GitHub Releases asset; mismatch fails the install step.
57-
CHAPEL_VERSION: '2.3.0'
58-
CHAPEL_DEB_URL: 'https://github.com/chapel-lang/chapel/releases/download/2.3.0/chapel-2.3.0-1.ubuntu22.amd64.deb'
59-
CHAPEL_DEB_SHA256: 'b7c1a48cbf26cfc7ad9af22a84ea4becb1a5e2fd485ec4a1f5f8a69e8a7cd58c'
57+
# 2.8.0 (not 2.3.0): the 2.3.0 .deb is slim and omits the bundled LLVM
58+
# backend, so `chpl ... -lproven` fails the link phase with the cryptic
59+
# `error in exec: No such file or directory` when chpl tries to spawn a
60+
# clang/lld it doesn't ship. 2.8.0 ships a self-contained toolchain and
61+
# matches the version the PR author validated against locally — same
62+
# version proven's sister panic-attack repo's chapel-ci runs on.
63+
CHAPEL_VERSION: '2.8.0'
64+
CHAPEL_DEB_URL: 'https://github.com/chapel-lang/chapel/releases/download/2.8.0/chapel-2.8.0-1.ubuntu22.amd64.deb'
65+
CHAPEL_DEB_SHA256: '944a454b8a791f344312fcd261b562871159b74f5ef41d81e11833eb21d85f60'
6066
# Pinned Zig version (kept in sync with .github/workflows/zig-ffi.yml).
61-
ZIG_VERSION: '0.13.0'
67+
# Pinned Zig version. Matches ffi/zig/build.zig.zon's
68+
# `minimum_zig_version = "0.15.2"`.
69+
ZIG_VERSION: '0.15.2'
6270

6371
jobs:
6472
# ==========================================================================
@@ -124,42 +132,88 @@ jobs:
124132
with:
125133
path: proven
126134

127-
- name: Checkout idris2-zig-ffi dependency
128-
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
129-
with:
130-
repository: hyperpolymath/idris2-zig-ffi
131-
path: idris2-zig-ffi
132-
133135
- name: Setup Zig ${{ env.ZIG_VERSION }}
134136
uses: mlugg/setup-zig@e7d1537c378b83b8049f65dda471d87a2f7b2df2 # v1
135137
with:
136138
version: ${{ env.ZIG_VERSION }}
137139

138-
- name: Build libproven.so via Zig
140+
- name: Build libproven.so (WIRED-subset fixture)
139141
working-directory: proven/ffi/zig
140-
run: zig build -Dtarget=x86_64-linux-gnu
142+
# NOTE: ffi/zig/build.zig depends on `idris2_zig_ffi` (relative
143+
# path to a sibling estate repo). That dep is not resolvable
144+
# from a fresh clone in CI (the same issue blocks zig-ffi.yml on
145+
# main), and resolving it is out of scope for the Chapel pilot.
146+
# All exports in src/main.zig are pure-Zig — none consume
147+
# idris2_zig_ffi. We compile main.zig directly with
148+
# `zig build-lib -dynamic -lc`, producing a libproven.so with
149+
# the WIRED 5 symbols (proven_path_has_traversal,
150+
# proven_header_has_crlf, proven_free_string, proven_version,
151+
# proven_build_info). When proven#88 lands the real
152+
# Idris-backed exports, this step swaps to
153+
# `zig build -Dtarget=...`.
154+
run: |
155+
set -euo pipefail
156+
mkdir -p zig-out/lib
157+
zig build-lib -dynamic -O ReleaseSafe \
158+
-fPIC \
159+
--name proven \
160+
-femit-bin=zig-out/lib/libproven.so \
161+
src/main.zig -lc
162+
ls -la zig-out/lib/
163+
nm -D --defined-only zig-out/lib/libproven.so \
164+
| awk '$2 ~ /^[TWV]$/ { print $3 }' \
165+
| grep -E '^(proven_path_has_traversal|proven_header_has_crlf|proven_free_string|proven_version|proven_build_info)$' \
166+
| sort -u | tee /tmp/wired-exports.txt
167+
test "$(wc -l < /tmp/wired-exports.txt)" -eq 5
141168
142169
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
143170
run: |
144171
set -euo pipefail
145-
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
172+
# libunwind-dev is a runtime link-time dep for chpl-built binaries
173+
# on the ubuntu .deb path; sibling estate repo (panic-attack)
174+
# established this preinstall as the working pattern.
175+
sudo apt-get update -qq
176+
sudo apt-get install -y libunwind-dev
177+
curl -fsSL --retry 3 -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
146178
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
147-
sudo apt-get update
148179
sudo apt-get install -y /tmp/chapel.deb
180+
# Chapel 2.8.0 .deb auto-configures its target tuple; no
181+
# CHPL_TARGET_* overrides needed (the 2.3.0-era runtime-tuple
182+
# mismatch is gone — verified against panic-attack chapel-ci).
149183
chpl --version
150184
151-
- name: Chapel module typecheck (--library on every module)
185+
- name: Chapel typecheck via driver build
152186
working-directory: proven/bindings/chapel
187+
env:
188+
PROVEN_LIB_PATH: ${{ github.workspace }}/proven/ffi/zig/zig-out/lib
189+
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/proven/bindings/c/include
153190
run: |
154191
set -euo pipefail
155-
for m in src/*.chpl; do
156-
echo "[chapel-build] typecheck $m"
157-
chpl --library --dynamic --print-callstack-on-error \
158-
--no-codegen \
159-
-M src/ \
160-
-I "$GITHUB_WORKSPACE/proven/bindings/c/include" \
161-
"$m"
162-
done
192+
# `chpl --no-codegen` against an extern-record Chapel module fails
193+
# in chapel/2.3.0 .deb installs with
194+
# error: Could not find bundled module 'ChapelSysCTypes'
195+
# because module resolution looks up
196+
# $CHPL_HOME/modules/standard/gen/$tuple/ChapelSysCTypes.chpl
197+
# and `--no-codegen` does not invoke the runtime-aware target-tuple
198+
# probe. Instead, do a real chpl link against libproven.so; that
199+
# exercises the same typechecker without the broken gen-path
200+
# resolution, AND additionally proves the binding links — which is
201+
# what we actually care about.
202+
# --print-commands surfaces the linker invocation chpl synthesises;
203+
# without it, link failures appear only as "error: Make Binary -
204+
# Linking" with no actionable detail.
205+
chpl -o /tmp/_chapel_typecheck_driver \
206+
smoke/hello_smoke.chpl \
207+
-M src/ \
208+
-I "$PROVEN_INCLUDE_PATH" \
209+
-L "$PROVEN_LIB_PATH" -lproven \
210+
--ldflags "-Wl,-rpath,$PROVEN_LIB_PATH" \
211+
--print-commands \
212+
--print-callstack-on-error
213+
ls -la /tmp/_chapel_typecheck_driver
214+
# Sanity check: the binary actually runs against the test fixture.
215+
LD_LIBRARY_PATH="$PROVEN_LIB_PATH" /tmp/_chapel_typecheck_driver
216+
echo "[chapel-build] typecheck via real link: OK"
163217
164218
- name: Upload libproven.so artefact
165219
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
@@ -217,10 +271,12 @@ jobs:
217271
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
218272
run: |
219273
set -euo pipefail
220-
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
274+
sudo apt-get update -qq
275+
sudo apt-get install -y libunwind-dev
276+
curl -fsSL --retry 3 -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
221277
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
222-
sudo apt-get update
223278
sudo apt-get install -y /tmp/chapel.deb
279+
chpl --version
224280
225281
- name: Install just
226282
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
@@ -266,10 +322,12 @@ jobs:
266322
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
267323
run: |
268324
set -euo pipefail
269-
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
325+
sudo apt-get update -qq
326+
sudo apt-get install -y libunwind-dev
327+
curl -fsSL --retry 3 -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
270328
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
271-
sudo apt-get update
272329
sudo apt-get install -y /tmp/chapel.deb
330+
chpl --version
273331
274332
- name: Install just
275333
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
@@ -327,10 +385,12 @@ jobs:
327385
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
328386
run: |
329387
set -euo pipefail
330-
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
388+
sudo apt-get update -qq
389+
sudo apt-get install -y libunwind-dev
390+
curl -fsSL --retry 3 -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
331391
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
332-
sudo apt-get update
333392
sudo apt-get install -y /tmp/chapel.deb
393+
chpl --version
334394
335395
- name: Install just
336396
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2

bindings/c/include/proven.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,20 @@ uint32_t proven_version_patch(void);
169169
/** @brief Get total module count. */
170170
uint32_t proven_module_count(void);
171171

172+
/**
173+
* @brief Get the library version string (NUL-terminated, statically allocated).
174+
*
175+
* The returned pointer is owned by the library; do NOT free it.
176+
*/
177+
const char* proven_version(void);
178+
179+
/**
180+
* @brief Get build information (compiler, target, etc.; NUL-terminated, static).
181+
*
182+
* The returned pointer is owned by the library; do NOT free it.
183+
*/
184+
const char* proven_build_info(void);
185+
172186
/* ============================================================================
173187
* SafeMath - Arithmetic that cannot crash (9 functions)
174188
* ============================================================================ */

bindings/chapel/README.adoc

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ just test
7474
use Proven;
7575
7676
// WIRED: links + runs against libproven 0.9.0.
77-
var trav = SafePath.hasTraversal("../../etc/passwd"); // some(true)
78-
var crlf = SafeHeader.hasCrlf("v\r\nX-Inj: 1"); // some(true)
77+
// WIRED: links + runs against libproven 0.9.0.
78+
var trav = SafePath.hasTraversal("../../etc/passwd"); // Maybe(bool): some(true)
79+
var crlf = SafeHeader.hasCrlf("v\r\nX-Inj: 1"); // Maybe(bool): some(true)
80+
if trav.present && trav.value then writeln("blocked");
7981
8082
// Identify the linked libproven (debug-friendly).
8183
writeln("libproven version: ", libraryVersion());
@@ -88,21 +90,35 @@ writeln("libproven version: ", libraryVersion());
8890

8991
== Error Handling
9092

91-
All fallible operations return Chapel optional types (`T?`). Use pattern
92-
matching to handle errors:
93+
All fallible operations return `Maybe(T)` — a binding-provided record
94+
with a `present` flag and a `value`. Chapel 2.x's built-in `T?`
95+
postfix syntax is class-only and does NOT compile for value types
96+
(`bool?`, `int?`, `string?` all error); `Maybe(T)` is the
97+
binding-tier replacement.
9398

9499
[source,chapel]
95100
----
96101
var result = SafePath.hasTraversal(userInput);
97-
if result == none {
102+
if !result.present {
98103
writeln("FFI error reading input");
99-
} else if result! {
104+
} else if result.value {
100105
writeln("Path traversal detected — refusing");
101106
} else {
102107
writeln("Path is safe");
103108
}
104109
----
105110

111+
The constructors are `some(value)` and `absent(T)`:
112+
113+
[source,chapel]
114+
----
115+
return some(true); // Maybe(bool) with present == true
116+
return absent(int(64)); // Maybe(int(64)) with present == false
117+
----
118+
119+
(`absent` rather than `none`/`nothing` because both of those are
120+
reserved words in Chapel.)
121+
106122
== Coverage matrix (WIRED vs GATED)
107123

108124
WIRED — present in `libproven 0.9.0`, verified by `chapel-symbol-audit`,

bindings/chapel/smoke/hello_smoke.chpl

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
//
44
// hello_smoke.chpl - End-to-end "does the binding link and run" smoke test.
55
//
6-
// Builds:
7-
// chpl -o hello_smoke smoke/hello_smoke.chpl -M src/ -lproven
8-
//
96
// Runs in well under 2s and is kept green by chapel-ci.yml even if larger
10-
// jobs go red. This is the "the binding actually works" gate.
7+
// jobs go red.
118
//
129
// Calls the WIRED subset only. Lifecycle (provenInit/Deinit) is GATED on
1310
// proven#88 because the Zig export ABI does not yet match the proven.h
@@ -16,34 +13,28 @@
1613
use Proven;
1714

1815
proc main(): int {
19-
// SafePath.hasTraversal — happy path (no traversal) and detection path.
20-
const safePath = "/var/www/html/index.html";
21-
const evilPath = "../../etc/passwd";
22-
23-
const okSafe = SafePath.hasTraversal(safePath);
24-
const okEvil = SafePath.hasTraversal(evilPath);
16+
// SafePath.hasTraversal — happy + detection paths.
17+
const okSafe = SafePath.hasTraversal("/var/www/html/index.html");
18+
const okEvil = SafePath.hasTraversal("../../etc/passwd");
2519

26-
if okSafe == none || okSafe! != false {
27-
writeln("FAIL: SafePath.hasTraversal('", safePath, "') did not return some(false)");
20+
if !okSafe.present || okSafe.value != false {
21+
writeln("FAIL: SafePath.hasTraversal('/var/www/html/index.html') did not return some(false)");
2822
return 1;
2923
}
30-
if okEvil == none || okEvil! != true {
31-
writeln("FAIL: SafePath.hasTraversal('", evilPath, "') did not return some(true)");
24+
if !okEvil.present || okEvil.value != true {
25+
writeln("FAIL: SafePath.hasTraversal('../../etc/passwd') did not return some(true)");
3226
return 1;
3327
}
3428

35-
// SafeHeader.hasCrlf — happy path (no CRLF) and detection path.
36-
const safeHeader = "application/json";
37-
const evilHeader = "value\r\nX-Injected: hi";
38-
39-
const headerOkSafe = SafeHeader.hasCrlf(safeHeader);
40-
const headerOkEvil = SafeHeader.hasCrlf(evilHeader);
29+
// SafeHeader.hasCrlf — happy + detection paths.
30+
const headerOkSafe = SafeHeader.hasCrlf("application/json");
31+
const headerOkEvil = SafeHeader.hasCrlf("value\r\nX-Injected: hi");
4132

42-
if headerOkSafe == none || headerOkSafe! != false {
43-
writeln("FAIL: SafeHeader.hasCrlf('", safeHeader, "') did not return some(false)");
33+
if !headerOkSafe.present || headerOkSafe.value != false {
34+
writeln("FAIL: SafeHeader.hasCrlf('application/json') did not return some(false)");
4435
return 1;
4536
}
46-
if headerOkEvil == none || headerOkEvil! != true {
37+
if !headerOkEvil.present || headerOkEvil.value != true {
4738
writeln("FAIL: SafeHeader.hasCrlf(<CRLF>) did not return some(true)");
4839
return 1;
4940
}

bindings/chapel/src/LibProven.chpl

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ module LibProven {
3535
// Status codes (used by both WIRED and GATED results)
3636
// ========================================================================
3737

38-
/** Status codes returned by Proven operations. */
39-
enum ProvenStatus : int(32) {
38+
/** Status codes returned by Proven operations.
39+
*
40+
* Chapel 2.x removed the `enum X : int(32) { ... }` type-spec syntax;
41+
* the C ABI's int32 status field is the source of truth, and this
42+
* enum is for human-readable documentation. Use `.toInt(32)` (or
43+
* compare via the `isOk` helper) when interoperating with FFI.
44+
*/
45+
enum ProvenStatus {
4046
OK = 0,
4147
ErrNullPointer = -1,
4248
ErrInvalidArgument = -2,
@@ -266,6 +272,37 @@ module LibProven {
266272
ptr: c_ptrConst(uint(8)), len: c_size_t
267273
): FloatResult;
268274

275+
// ========================================================================
276+
// Maybe(T) — Chapel-2.x-compatible optional-type pattern.
277+
//
278+
// Chapel's `T?` postfix is class-only; value-type wrappers
279+
// (`bool?` / `int(64)?` / `string?`) do NOT compile. Maybe(T) is a
280+
// generic record with a `present` flag and a `value`. `some(v)` and
281+
// `none(T)` are the constructors; call sites pattern-match via
282+
// `if r.present then r.value else fallback`.
283+
// ========================================================================
284+
285+
/** Optional value-type carrier — see module-level note. */
286+
record Maybe {
287+
type T;
288+
var present : bool;
289+
var value : T;
290+
}
291+
292+
/** Wrap a value as a present Maybe(T). */
293+
inline proc some(in v): Maybe(v.type) {
294+
return new Maybe(v.type, true, v);
295+
}
296+
297+
/** Construct an absent Maybe(T). Pass the type explicitly:
298+
* `return absent(bool)` / `absent(int(64))` / `absent(string)`.
299+
*
300+
* (Named `absent` because Chapel reserves `none` and `nothing`.) */
301+
inline proc absent(type T): Maybe(T) {
302+
var dflt : T;
303+
return new Maybe(T, false, dflt);
304+
}
305+
269306
// ========================================================================
270307
// Helper: convert Chapel string to C pointer + length.
271308
// ========================================================================
@@ -280,18 +317,27 @@ module LibProven {
280317
return status == 0;
281318
}
282319

283-
/** Extract a Chapel string from a StringResult and free the C memory. */
320+
/** Extract a Chapel string from a StringResult and free the C memory.
321+
*
322+
* ``string.createCopyingBuffer`` is throwing; we use ``try!`` because
323+
* the C ABI guarantees a valid UTF-8 buffer of declared length when
324+
* status is OK. An empty status / nil pointer short-circuits to "". */
284325
proc extractString(ref r: StringResult): string {
285326
if !isOk(r.status) || r.value == nil then return "";
286-
var s = string.createCopyingBuffer(r.value: c_ptrConst(c_char), r.length: int);
327+
var s = try! string.createCopyingBuffer(r.value: c_ptrConst(c_char),
328+
r.length: int);
287329
provenFreeString(r.value);
288330
return s;
289331
}
290332

291-
/** Read a NUL-terminated C string returned by libproven (no free required). */
333+
/** Read a NUL-terminated C string returned by libproven (no free required).
334+
*
335+
* Uses ``try!`` for the same reason as ``extractString``: libproven
336+
* guarantees NUL-termination on the string accessors that call this
337+
* (proven_version, proven_build_info). */
292338
proc cStringToChapel(p: c_ptrConst(c_char)): string {
293339
if p == nil then return "";
294-
return string.createCopyingBuffer(p);
340+
return try! string.createCopyingBuffer(p);
295341
}
296342

297343
/** Library version string from the loaded libproven. */

0 commit comments

Comments
 (0)