Skip to content

Commit ba14ff9

Browse files
authored
Merge pull request #2 from SwayamInSync/contract-keywords
Extend clang lexer to support contract-keywords and gate behind `-fverify-contracts`
2 parents 007f107 + 8f0a8cc commit ba14ff9

9 files changed

Lines changed: 253 additions & 33 deletions

File tree

‎README.md‎

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,102 @@
1-
# The LLVM Compiler Infrastructure
1+
# CppVerify - Extending C++ to support Program Verification using SMT solvers
22

3-
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/llvm/llvm-project/badge)](https://securityscorecards.dev/viewer/?uri=github.com/llvm/llvm-project)
4-
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8273/badge)](https://www.bestpractices.dev/projects/8273)
5-
[![libc++](https://github.com/llvm/llvm-project/actions/workflows/libcxx-build-and-test.yaml/badge.svg?branch=main&event=schedule)](https://github.com/llvm/llvm-project/actions/workflows/libcxx-build-and-test.yaml?query=event%3Aschedule)
3+
> [!CAUTION]
4+
> **Work in progress** — only lexer support is implemented so far. The verifier is not yet functional.
65
7-
Welcome to the LLVM project!
6+
> This is a fork of [LLVM/Clang](https://github.com/llvm/llvm-project) (pinned to
7+
> `llvmorg-22.1.3`) extended with **CppVerify**: a deductive verification system
8+
> for C++ built directly into the Clang frontend.
89
9-
This repository contains the source code for LLVM, a toolkit for the
10-
construction of highly optimized compilers, optimizers, and run-time
11-
environments.
10+
CppVerify adds first-class contract syntax (`pre`, `post`, `invariant`,
11+
`decreases`, `ghost`, `spec`, `proof`) to C++, type-checked by Clang Sema and
12+
discharged by Z3 via a weakest-precondition calculus backend. It occupies an
13+
entirely uncontested niche: the only deductive verifier for modern C++ built on
14+
Clang, with native contract syntax rather than comments or macros.
1215

13-
The LLVM project has multiple components. The core of the project is
14-
itself called "LLVM". This contains all of the tools, libraries, and header
15-
files needed to process intermediate representations and convert them into
16-
object files. Tools include an assembler, disassembler, bitcode analyzer, and
17-
bitcode optimizer.
16+
## What it looks like
1817

19-
C-like languages use the [Clang](https://clang.llvm.org/) frontend. This
20-
component compiles C, C++, Objective-C, and Objective-C++ code into LLVM bitcode
21-
-- and from there into object files, using LLVM.
18+
```cpp
19+
spec int fibo(int n) decreases(n) {
20+
if (n <= 1) return n;
21+
return fibo(n - 1) + fibo(n - 2);
22+
}
2223

23-
Other components include:
24-
the [libc++ C++ standard library](https://libcxx.llvm.org),
25-
the [LLD linker](https://lld.llvm.org), and more.
24+
int safe_fib(int n)
25+
pre(n >= 0)
26+
pre(n <= 45)
27+
post(result == fibo(n))
28+
{
29+
if (n <= 1) return n;
30+
int a = 0, b = 1, i = 2;
31+
while (i <= n)
32+
invariant(2 <= i && i <= n + 1)
33+
invariant(b == fibo(i - 1))
34+
decreases(n - i + 1)
35+
{
36+
int tmp = a + b;
37+
a = b;
38+
b = tmp;
39+
i++;
40+
}
41+
return b;
42+
}
43+
```
2644
27-
## Getting the Source Code and Building LLVM
45+
## Contract syntax reference
2846
29-
Consult the
30-
[Getting Started with LLVM](https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm)
31-
page for information on building and running LLVM.
47+
| Syntax | Where | Meaning |
48+
| ------------------------- | --------------------------------- | -------------------------------------------------------------------- |
49+
| `pre(expr)` | After function `)` | Precondition — caller must satisfy |
50+
| `post(expr)` | After function `)` | Postcondition — callee must establish; may use `result` and `old(x)` |
51+
| `invariant(expr)` | After `while`/`for` condition `)` | Loop invariant |
52+
| `decreases(expr)` | After `while`/`for` condition `)` | Termination measure |
53+
| `ghost { ... }` | Statement | Ghost block — proof steps, stripped by CodeGen |
54+
| `contract_assert(expr)` | Statement | Verification condition (not a runtime check) |
55+
| `spec T f(...)` | Declaration | Pure spec function — interpreted by verifier only |
56+
| `proof void f(...)` | Declaration | Ghost proof function — establishes lemmas |
57+
| `forall(i, lo, hi, expr)` | Expression | Bounded universal quantifier |
58+
| `exists(i, lo, hi, expr)` | Expression | Bounded existential quantifier |
59+
| `old(expr)` | Inside `post` | Value of `expr` at function entry |
60+
| `result` | Inside `post` | Return value of the enclosing function |
3261
33-
For information on how to contribute to the LLVM project, please take a look at
34-
the [Contributing to LLVM](https://llvm.org/docs/Contributing.html) guide.
62+
All contract syntax is gated behind `-fverify-contracts`. Without the flag,
63+
none of these names are reserved — existing C++ that uses `pre`, `post`, etc.
64+
as identifiers compiles unchanged.
3565
36-
## Getting in touch
66+
## Architecture
3767
38-
Join the [LLVM Discourse forums](https://discourse.llvm.org/), [Discord
39-
chat](https://discord.gg/xS7Z362),
40-
[LLVM Office Hours](https://llvm.org/docs/GettingInvolved.html#office-hours) or
41-
[Regular sync-ups](https://llvm.org/docs/GettingInvolved.html#online-sync-ups).
68+
```
69+
C++ source with contracts
70+
│
71+
▼
72+
Clang Frontend (modified Parser + Sema + AST)
73+
│ Annotated AST with contract nodes
74+
▼
75+
ASTConverter (Clang AST → Layer 1 VCR IR)
76+
│ Typed, control-flow-preserving IR
77+
▼
78+
Passivize (Layer 1 → Layer 2 SSA)
79+
│ Havoc / assume / assert, no loops
80+
▼
81+
WP Calculus (weakest precondition, backward pass)
82+
│ Verification conditions
83+
▼
84+
Z3 Encoding (VCs → Z3 formulas, check UNSAT)
85+
│ sat / unsat / unknown
86+
▼
87+
Diagnostics (Clang-style errors + counterexamples)
88+
```
4289
43-
The LLVM project has adopted a [code of conduct](https://llvm.org/docs/CodeOfConduct.html) for
44-
participants to all modes of communication within the project.
90+
Normal compilation skips everything after the frontend: CodeGen simply ignores
91+
all ghost/contract AST nodes.
92+
93+
## Competitive landscape
94+
95+
| Tool | Frontend | C++ support | Contract syntax |
96+
| ------------- | --------------------- | -------------- | --------------- |
97+
| Frama-C | Custom OCaml | Prototype only | ACSL comments |
98+
| VCC | Custom | C only (dead) | Macros |
99+
| VeriFast | Custom | No | Comments |
100+
| CBMC | Custom goto-cc | Partial | Assertions only |
101+
| Verus | Rust compiler | No (Rust only) | First-class |
102+
| **CppVerify** | **Clang (this fork)** | **Native** | **First-class** |

‎clang/include/clang/Basic/IdentifierTable.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ enum TokenKey : unsigned {
7878
KEYHLSL = 0x8000000,
7979
KEYFIXEDPOINT = 0x10000000,
8080
KEYDEFERTS = 0x20000000,
81-
KEYMAX = KEYDEFERTS, // The maximum key
81+
KEYCONTRACT = 0x40000000, // Keywords enabled by -fverify-contracts
82+
KEYMAX = KEYCONTRACT, // The maximum key
8283
KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20,
8384
KEYALL = (KEYMAX | (KEYMAX - 1)) & ~KEYNOMS18 & ~KEYNOOPENCL &
8485
~KEYNOZOS // KEYNOMS18, KEYNOOPENCL, KEYNOZOS are excluded.

‎clang/include/clang/Basic/LangOptions.def‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ VALUE_LANGOPT(FunctionAlignment, 5, 0, Compatible, "Default alignment for functi
427427
VALUE_LANGOPT(LoopAlignment, 32, 0, Compatible, "Default alignment for loops")
428428

429429
LANGOPT(FixedPoint, 1, 0, NotCompatible, "fixed point types")
430+
431+
LANGOPT(VerifyContracts, 1, 0, NotCompatible, "CppVerify contract verification")
430432
LANGOPT(PaddingOnUnsignedFixedPoint, 1, 0, NotCompatible,
431433
"unsigned fixed point types having one extra padding bit")
432434

‎clang/include/clang/Basic/TokenKinds.def‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ PUNCTUATOR(greatergreatergreater, ">>>")
295295
// extension.
296296
// KEYDEFERTS - This is a keyword if the C '_Defer' TS is enabled
297297
// KEYZOS - This is a keyword in C/C++ on z/OS
298+
// KEYCONTRACT - This is a keyword if -fverify-contracts is enabled
298299
//
299300
KEYWORD(auto , KEYALL)
300301
KEYWORD(break , KEYALL)
@@ -445,6 +446,20 @@ C23_KEYWORD(typeof_unqual , 0)
445446
// '_Defer' TS
446447
KEYWORD(_Defer , KEYDEFERTS)
447448

449+
// CppVerify contract keywords (enabled by -fverify-contracts)
450+
KEYWORD(pre , KEYCONTRACT)
451+
KEYWORD(post , KEYCONTRACT)
452+
KEYWORD(invariant , KEYCONTRACT)
453+
KEYWORD(decreases , KEYCONTRACT)
454+
KEYWORD(ghost , KEYCONTRACT)
455+
KEYWORD(spec , KEYCONTRACT)
456+
KEYWORD(proof , KEYCONTRACT)
457+
KEYWORD(contract_assert , KEYCONTRACT)
458+
KEYWORD(forall , KEYCONTRACT)
459+
KEYWORD(exists , KEYCONTRACT)
460+
KEYWORD(old , KEYCONTRACT)
461+
KEYWORD(result , KEYCONTRACT)
462+
448463
// ISO/IEC JTC1 SC22 WG14 N1169 Extension
449464
KEYWORD(_Accum , KEYFIXEDPOINT)
450465
KEYWORD(_Fract , KEYFIXEDPOINT)

‎clang/include/clang/Options/Options.td‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,6 +2366,13 @@ defm fixed_point : BoolFOption<"fixed-point",
23662366
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
23672367
NegFlag<SetFalse, [], [ClangOption], "Disable">,
23682368
BothFlags<[], [ClangOption], " fixed point types">>;
2369+
2370+
// CppVerify: deductive contract verification
2371+
defm verify_contracts : BoolFOption<"verify-contracts",
2372+
LangOpts<"VerifyContracts">, DefaultFalse,
2373+
PosFlag<SetTrue, [], [ClangOption, CC1Option],
2374+
"Enable CppVerify contract syntax (pre/post/invariant/decreases/ghost/spec/proof)">,
2375+
NegFlag<SetFalse, [], [ClangOption], "Disable CppVerify contract syntax">>;
23692376
def cxx_static_destructors_EQ : Joined<["-"], "fc++-static-destructors=">, Group<f_Group>,
23702377
HelpText<"Controls which variables C++ static destructors are registered for">,
23712378
Values<"all,thread-local,none">,

‎clang/lib/Basic/IdentifierTable.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts,
166166
return LangOpts.FixedPoint ? KS_Enabled : KS_Disabled;
167167
case KEYDEFERTS:
168168
return LangOpts.DeferTS ? KS_Enabled : KS_Disabled;
169+
case KEYCONTRACT:
170+
return LangOpts.VerifyContracts ? KS_Enabled : KS_Disabled;
169171
default:
170172
llvm_unreachable("Unknown KeywordStatus flag");
171173
}

‎clang/lib/Driver/ToolChains/Clang.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6365,6 +6365,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
63656365
Args.addOptInFlag(CmdArgs, options::OPT_ffixed_point,
63666366
options::OPT_fno_fixed_point);
63676367

6368+
Args.addOptInFlag(CmdArgs, options::OPT_fverify_contracts,
6369+
options::OPT_fno_verify_contracts);
6370+
63686371
if (Arg *A = Args.getLastArg(options::OPT_fcxx_abi_EQ))
63696372
A->render(Args, CmdArgs);
63706373

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Test that -fverify-contracts is forwarded from the driver to cc1, and that
2+
// the flag is absent by default (or when -fno-verify-contracts is given).
3+
4+
// RUN: %clang -### -std=c++20 -fverify-contracts %s 2>&1 \
5+
// RUN: | FileCheck %s --check-prefix=WITH
6+
7+
// RUN: %clang -### -std=c++20 %s 2>&1 \
8+
// RUN: | FileCheck %s --check-prefix=OFF
9+
10+
// RUN: %clang -### -std=c++20 -fno-verify-contracts %s 2>&1 \
11+
// RUN: | FileCheck %s --check-prefix=OFF
12+
13+
// -fverify-contracts must appear in the cc1 invocation.
14+
// WITH: "-fverify-contracts"
15+
16+
// Without the flag (or with the explicit negation), neither form is forwarded.
17+
// OFF-NOT: "-fverify-contracts"
18+
// OFF-NOT: "-fno-verify-contracts"
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Verify that all 12 KEYCONTRACT keywords are lexed correctly.
2+
//
3+
// With -fverify-contracts: each name lexes as a keyword token.
4+
// Without -fverify-contracts: each name lexes as an identifier (no conflict
5+
// with existing C++ code).
6+
7+
// --- dump-tokens: WITH flag — expect keyword tokens ---
8+
// RUN: %clang_cc1 -std=c++20 -fverify-contracts -dump-tokens %s 2>&1 \
9+
// RUN: | FileCheck %s --check-prefix=KW
10+
11+
// --- dump-tokens: WITHOUT flag — expect identifier tokens ---
12+
// RUN: %clang_cc1 -std=c++20 -dump-tokens %s 2>&1 \
13+
// RUN: | FileCheck %s --check-prefix=ID
14+
15+
// --- __is_identifier: WITH flag (-DWITH_FLAG selects the right assertions) ---
16+
// RUN: %clang_cc1 -std=c++20 -fverify-contracts -DWITH_FLAG -fsyntax-only %s
17+
18+
// --- __is_identifier: WITHOUT flag ---
19+
// RUN: %clang_cc1 -std=c++20 -fsyntax-only %s
20+
21+
// ==========================================================================
22+
// Section 1: FileCheck patterns for dump-tokens runs
23+
//
24+
// dump-tokens is lex-only; syntax does not matter. The tokens that satisfy
25+
// these checks come from the variable declarations in Section 3, which are
26+
// compiled by every dump-tokens run (neither run defines WITH_FLAG).
27+
//
28+
// KW-DAG checks that each name appears as a keyword token.
29+
// ID-DAG checks that each name appears as an identifier token.
30+
// ==========================================================================
31+
32+
// KW-DAG: pre 'pre'
33+
// KW-DAG: post 'post'
34+
// KW-DAG: invariant 'invariant'
35+
// KW-DAG: decreases 'decreases'
36+
// KW-DAG: ghost 'ghost'
37+
// KW-DAG: spec 'spec'
38+
// KW-DAG: proof 'proof'
39+
// KW-DAG: contract_assert 'contract_assert'
40+
// KW-DAG: forall 'forall'
41+
// KW-DAG: exists 'exists'
42+
// KW-DAG: old 'old'
43+
// KW-DAG: result 'result'
44+
45+
// ID-DAG: identifier 'pre'
46+
// ID-DAG: identifier 'post'
47+
// ID-DAG: identifier 'invariant'
48+
// ID-DAG: identifier 'decreases'
49+
// ID-DAG: identifier 'ghost'
50+
// ID-DAG: identifier 'spec'
51+
// ID-DAG: identifier 'proof'
52+
// ID-DAG: identifier 'contract_assert'
53+
// ID-DAG: identifier 'forall'
54+
// ID-DAG: identifier 'exists'
55+
// ID-DAG: identifier 'old'
56+
// ID-DAG: identifier 'result'
57+
58+
// ==========================================================================
59+
// Section 2: __is_identifier() static assertions
60+
//
61+
// __is_identifier(X) is a preprocessor built-in: it expands to 0 or 1
62+
// *before* the C++ parser runs, so the parser never sees the keyword token
63+
// inside the parens. The result is a pure integer literal.
64+
//
65+
// WITH_FLAG → -fverify-contracts active → all 12 are keywords → expect 0
66+
// !WITH_FLAG → flag absent → all 12 are identifiers → expect 1
67+
// ==========================================================================
68+
69+
#ifdef WITH_FLAG
70+
static_assert(!__is_identifier(pre), "pre must be a keyword with -fverify-contracts");
71+
static_assert(!__is_identifier(post), "post must be a keyword with -fverify-contracts");
72+
static_assert(!__is_identifier(invariant), "invariant must be a keyword with -fverify-contracts");
73+
static_assert(!__is_identifier(decreases), "decreases must be a keyword with -fverify-contracts");
74+
static_assert(!__is_identifier(ghost), "ghost must be a keyword with -fverify-contracts");
75+
static_assert(!__is_identifier(spec), "spec must be a keyword with -fverify-contracts");
76+
static_assert(!__is_identifier(proof), "proof must be a keyword with -fverify-contracts");
77+
static_assert(!__is_identifier(contract_assert), "contract_assert must be a keyword with -fverify-contracts");
78+
static_assert(!__is_identifier(forall), "forall must be a keyword with -fverify-contracts");
79+
static_assert(!__is_identifier(exists), "exists must be a keyword with -fverify-contracts");
80+
static_assert(!__is_identifier(old), "old must be a keyword with -fverify-contracts");
81+
static_assert(!__is_identifier(result), "result must be a keyword with -fverify-contracts");
82+
#else
83+
// ==========================================================================
84+
// Section 3: identifier-mode checks + token source for dump-tokens runs
85+
//
86+
// This block is compiled by:
87+
// - the two dump-tokens runs (no WITH_FLAG defined)
88+
// - the fsyntax-only run WITHOUT -fverify-contracts
89+
//
90+
// The variable declarations give dump-tokens the tokens to match against.
91+
// The static_assert lines verify backward compatibility.
92+
// ==========================================================================
93+
94+
// Backward compatibility: all names remain valid C++ identifiers.
95+
static_assert(__is_identifier(pre), "pre must be an identifier without -fverify-contracts");
96+
static_assert(__is_identifier(post), "post must be an identifier without -fverify-contracts");
97+
static_assert(__is_identifier(invariant), "invariant must be an identifier without -fverify-contracts");
98+
static_assert(__is_identifier(decreases), "decreases must be an identifier without -fverify-contracts");
99+
static_assert(__is_identifier(ghost), "ghost must be an identifier without -fverify-contracts");
100+
static_assert(__is_identifier(spec), "spec must be an identifier without -fverify-contracts");
101+
static_assert(__is_identifier(proof), "proof must be an identifier without -fverify-contracts");
102+
static_assert(__is_identifier(contract_assert), "contract_assert must be an identifier without -fverify-contracts");
103+
static_assert(__is_identifier(forall), "forall must be an identifier without -fverify-contracts");
104+
static_assert(__is_identifier(exists), "exists must be an identifier without -fverify-contracts");
105+
static_assert(__is_identifier(old), "old must be an identifier without -fverify-contracts");
106+
static_assert(__is_identifier(result), "result must be an identifier without -fverify-contracts");
107+
108+
// Declarations whose names produce the keyword/identifier tokens that the
109+
// dump-tokens FileCheck patterns match against. With -fverify-contracts each
110+
// name lexes as its keyword token; without it, as an identifier token.
111+
// (dump-tokens is lex-only so no parse error occurs in either case.)
112+
int pre, post, invariant, decreases, ghost, spec, proof;
113+
int contract_assert, forall, exists, old, result;
114+
#endif

0 commit comments

Comments
 (0)