Skip to content

Commit 1d4e595

Browse files
authored
Merge pull request #11 from SwayamInSync/feat/type-invariant-safe-fib-mvp
feat(verify): Adding lazy `type_invariant` support in cpp-verify
2 parents 3421639 + 6459677 commit 1d4e595

55 files changed

Lines changed: 1059 additions & 158 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ ninja -C build clang cpp-verify
5656

5757
```cpp
5858
int abs(int x)
59-
pre(true)
59+
pre(x >= -2147483647) // every int except INT_MIN, whose negation overflows
6060
post(result >= 0)
6161
{
6262
return x < 0 ? -x : x;
@@ -70,6 +70,23 @@ int abs(int x)
7070

7171
Use `-fverify-contracts` on `clang++` so `pre` / `post` are keywords. `cpp-verify` enables that flag automatically.
7272

73+
### Supported compiler
74+
75+
Contract syntax (`pre` / `post` / `invariant` / `spec` / …) and the
76+
`-fverify-contracts` flag exist **only in this repository's Clang**. To compile
77+
or verify code that uses contracts, you must use the shipped tools:
78+
79+
- `./build/bin/cpp-verify file.cpp` — verify.
80+
- `./build/bin/clang++ -fverify-contracts … file.cpp` — compile (also runs verification).
81+
82+
Stock GCC or upstream Clang will reject `-fverify-contracts` (unknown flag) **and**
83+
the contract keywords (`expected function body after function declarator` at
84+
`pre(...)`). There is no contract support outside the shipped Clang.
85+
86+
Note this is a separate matter from *building* cpp-verify itself from source:
87+
that bootstrap step compiles ordinary C++ and works with **any** standard host
88+
compiler — GCC or Clang (`setup.sh` uses `${CXX:-c++}`).
89+
7390
## Verification backends
7491

7592
| Backend | CLI | Role |
@@ -84,7 +101,9 @@ Use `-fverify-contracts` on `clang++` so `pre` / `post` are keywords. `cpp-verif
84101
|---------|------|
85102
| `cpp-verify file.cpp` | Verify only (Z3) |
86103
| `clang++ -fverify-contracts -c file.cpp` | Verify (parallel) + compile |
87-
| `clang++ -fverify-contracts -fno-verify -c file.cpp` | Contracts on; skip SMT |
104+
| `clang++ -fno-verify -c file.cpp` | Light check — contracts on (implied), skip the solver |
105+
106+
`-fverify-contracts` and `-fno-verify` are two axes: the first enables the contract language (and verifies by default); `-fno-verify` skips the solver and implies `-fverify-contracts`, so a lone `-fno-verify` is a fast syntax/semantics check. There is no `-fverify`.
88107

89108
```bash
90109
./build/bin/cpp-verify --dump-ir=1,2,3,4 file.cpp # VCR, passive, VC, Z3

‎clang/cmake/modules/CppVerifyZ3.cmake‎

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,70 @@ set(CPPVERIFY_Z3_SOURCE_DIR "" CACHE PATH "Path to a Z3 source tree (overrides t
2626
get_filename_component(CPPVERIFY_REPO_ROOT
2727
"${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
2828

29-
function(cppverify_z3_apply_build_options)
30-
set(Z3_BUILD_LIBZ3_SHARED OFF CACHE BOOL "" FORCE)
31-
set(Z3_BUILD_EXECUTABLE OFF CACHE BOOL "" FORCE)
32-
set(Z3_BUILD_TEST_EXECUTABLES OFF CACHE BOOL "" FORCE)
33-
endfunction()
34-
35-
function(cppverify_z3_register_alias)
36-
if(TARGET libz3)
37-
if(NOT TARGET cppverify_z3)
38-
add_library(cppverify_z3 ALIAS libz3)
39-
endif()
40-
elseif(TARGET z3)
41-
if(NOT TARGET cppverify_z3)
42-
add_library(cppverify_z3 ALIAS z3)
43-
endif()
29+
# Build vendored Z3 as an isolated ExternalProject rather than add_subdirectory.
30+
#
31+
# Why ExternalProject and not add_subdirectory: Z3's CMake declares a library
32+
# component target literally named `opt` (z3_add_component(opt ...)). LLVM's
33+
# monorepo also declares an `opt` executable (llvm/tools/opt). Pulling Z3 into
34+
# the same CMake project via add_subdirectory/FetchContent makes both `opt`
35+
# targets live in one namespace and configure fails with CMP0002 (duplicate
36+
# target). ExternalProject runs Z3's CMake in a separate build, so its targets
37+
# never collide with LLVM's. We then consume the installed static lib through an
38+
# IMPORTED target.
39+
#
40+
# Sources for the build (either an on-disk tree or a git clone) are handled by
41+
# the SOURCE_DIR / GIT_REPOSITORY arguments threaded in by the callers.
42+
function(cppverify_z3_build_external)
43+
cmake_parse_arguments(Z3EP "" "SOURCE_DIR;GIT_REPOSITORY;GIT_TAG" "" ${ARGN})
44+
include(ExternalProject)
45+
46+
set(_prefix "${CMAKE_BINARY_DIR}/cppverify-z3")
47+
set(_install "${_prefix}/install")
48+
set(_incdir "${_install}/include")
49+
set(_libpath "${_install}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}z3${CMAKE_STATIC_LIBRARY_SUFFIX}")
50+
51+
if(Z3EP_SOURCE_DIR)
52+
set(_src_args SOURCE_DIR "${Z3EP_SOURCE_DIR}")
53+
message(STATUS "CppVerify: building vendored Z3 (ExternalProject) from ${Z3EP_SOURCE_DIR}")
4454
else()
45-
message(FATAL_ERROR "Z3 build did not produce libz3 or z3 CMake target")
55+
set(_src_args
56+
GIT_REPOSITORY "${Z3EP_GIT_REPOSITORY}"
57+
GIT_TAG "${Z3EP_GIT_TAG}"
58+
GIT_SHALLOW TRUE)
59+
message(STATUS "CppVerify: building vendored Z3 (ExternalProject) ${Z3EP_GIT_TAG} "
60+
"(first build needs network)")
4661
endif()
47-
endfunction()
4862

49-
function(cppverify_z3_from_subdirectory z3_src binary_dir)
50-
cppverify_z3_apply_build_options()
51-
message(STATUS "CppVerify: building vendored Z3 from ${z3_src}")
52-
add_subdirectory("${z3_src}" "${binary_dir}" EXCLUDE_FROM_ALL)
53-
cppverify_z3_register_alias()
63+
ExternalProject_Add(cppverify_z3_ep
64+
${_src_args}
65+
PREFIX "${_prefix}"
66+
CMAKE_CACHE_ARGS
67+
-DCMAKE_BUILD_TYPE:STRING=Release
68+
-DCMAKE_INSTALL_PREFIX:PATH=${_install}
69+
-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
70+
-DZ3_BUILD_LIBZ3_SHARED:BOOL=OFF
71+
-DZ3_BUILD_EXECUTABLE:BOOL=OFF
72+
-DZ3_BUILD_TEST_EXECUTABLES:BOOL=OFF
73+
-DZ3_BUILD_DOCUMENTATION:BOOL=OFF
74+
-DZ3_ENABLE_EXAMPLE_TARGETS:BOOL=OFF
75+
BUILD_BYPRODUCTS "${_libpath}"
76+
USES_TERMINAL_DOWNLOAD TRUE
77+
USES_TERMINAL_BUILD TRUE)
78+
79+
# INTERFACE_INCLUDE_DIRECTORIES must exist at configure time.
80+
file(MAKE_DIRECTORY "${_incdir}")
81+
82+
find_package(Threads REQUIRED)
83+
add_library(cppverify_z3 STATIC IMPORTED GLOBAL)
84+
set_target_properties(cppverify_z3 PROPERTIES
85+
IMPORTED_LOCATION "${_libpath}"
86+
INTERFACE_INCLUDE_DIRECTORIES "${_incdir}"
87+
INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}")
88+
89+
# Ensure the ExternalProject is built before anything links the imported lib.
90+
# BUILD_BYPRODUCTS handles Ninja ordering; this property lets consuming targets
91+
# add an explicit dependency for the Makefiles generator too.
92+
set_property(GLOBAL PROPERTY CPPVERIFY_Z3_EP_TARGET cppverify_z3_ep)
5493
endfunction()
5594

5695
function(cppverify_z3_try_system out_var)
@@ -101,22 +140,14 @@ function(cppverify_z3_try_local out_var)
101140
set(${out_var} "" PARENT_SCOPE)
102141
return()
103142
endif()
104-
cppverify_z3_from_subdirectory("${_src}" "${CMAKE_BINARY_DIR}/cppverify-z3-build")
143+
cppverify_z3_build_external(SOURCE_DIR "${_src}")
105144
set(${out_var} cppverify_z3 PARENT_SCOPE)
106145
endfunction()
107146

108147
function(cppverify_z3_try_fetch out_var)
109-
include(FetchContent)
110-
cppverify_z3_apply_build_options()
111-
message(STATUS "CppVerify: fetching Z3 ${CPPVERIFY_Z3_GIT_TAG} (first build needs network)")
112-
FetchContent_Declare(
113-
cppverify_z3_src
148+
cppverify_z3_build_external(
114149
GIT_REPOSITORY https://github.com/Z3Prover/z3.git
115-
GIT_TAG ${CPPVERIFY_Z3_GIT_TAG}
116-
GIT_SHALLOW TRUE
117-
)
118-
FetchContent_MakeAvailable(cppverify_z3_src)
119-
cppverify_z3_register_alias()
150+
GIT_TAG ${CPPVERIFY_Z3_GIT_TAG})
120151
set(${out_var} cppverify_z3 PARENT_SCOPE)
121152
endfunction()
122153

‎clang/include/clang/AST/ASTContext.h‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ struct LoopContractInfo {
112112
SmallVector<Expr *, 2> Invariants;
113113
Expr *Decreases = nullptr;
114114
};
115+
116+
/// Type invariant clauses on a RecordDecl (CppVerify).
117+
struct TypeContractInfo {
118+
SmallVector<Expr *, 2> Invariants;
119+
};
115120
class CharUnits;
116121
class ConceptDecl;
117122
class CXXABI;
@@ -376,6 +381,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
376381
/// CppVerify: contract info for loops (invariant/decreases).
377382
llvm::DenseMap<const Stmt *, LoopContractInfo *> LoopContracts;
378383

384+
/// CppVerify: type_invariant clauses on record/class types.
385+
llvm::DenseMap<const RecordDecl *, TypeContractInfo *> TypeContracts;
386+
379387
/// Mapping from GUIDs to the corresponding MSGuidDecl.
380388
mutable llvm::FoldingSet<MSGuidDecl> MSGuidDecls;
381389

@@ -3457,6 +3465,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
34573465
/// CppVerify: get contract info for a loop, or nullptr if none.
34583466
const LoopContractInfo *getLoopContract(const Stmt *S) const;
34593467

3468+
/// CppVerify: get or create type_invariant info for a record type.
3469+
TypeContractInfo &getOrCreateTypeContract(const RecordDecl *RD);
3470+
/// CppVerify: get type_invariant info, or nullptr if none.
3471+
const TypeContractInfo *getTypeContract(const RecordDecl *RD) const;
3472+
34603473
/// Allocate an uninitialized TypeSourceInfo.
34613474
///
34623475
/// The caller should initialize the memory held by TypeSourceInfo using

‎clang/include/clang/Basic/DiagnosticParseKinds.td‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,5 +1898,9 @@ def err_result_outside_postcondition : Error<
18981898
"'result' can only be used in postconditions">;
18991899
def err_old_outside_postcondition : Error<
19001900
"'old' can only be used in postconditions">;
1901+
def warn_contract_member_function_unsupported : Warning<
1902+
"contracts on member functions are not yet supported and are ignored; "
1903+
"move the function out of the class or verify it as a free function">,
1904+
InGroup<DiagGroup<"contract-unsupported">>;
19011905

19021906
} // end of Parser diagnostics

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ KEYWORD(_Defer , KEYDEFERTS)
450450
KEYWORD(pre , KEYCONTRACT)
451451
KEYWORD(post , KEYCONTRACT)
452452
KEYWORD(invariant , KEYCONTRACT)
453+
KEYWORD(type_invariant , KEYCONTRACT)
453454
KEYWORD(decreases , KEYCONTRACT)
454455
KEYWORD(ghost , KEYCONTRACT)
455456
KEYWORD(spec , KEYCONTRACT)

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,20 +2367,25 @@ defm fixed_point : BoolFOption<"fixed-point",
23672367
NegFlag<SetFalse, [], [ClangOption], "Disable">,
23682368
BothFlags<[], [ClangOption], " fixed point types">>;
23692369

2370-
// CppVerify: deductive contract verification
2370+
// CppVerify: deductive contract verification.
2371+
// -fverify-contracts enables the contract language (pre/post/...) and, by
2372+
// default, runs the SMT verifier in parallel with code generation.
23712373
defm verify_contracts : BoolFOption<"verify-contracts",
23722374
LangOpts<"VerifyContracts">, DefaultFalse,
23732375
PosFlag<SetTrue, [], [ClangOption, CC1Option],
2374-
"Enable CppVerify contract syntax (pre/post/invariant/decreases/ghost/spec/proof)">,
2376+
"Enable CppVerify contracts (pre/post/invariant/decreases/ghost/spec/proof) "
2377+
"and verify in parallel with compilation">,
23752378
NegFlag<SetFalse, [], [ClangOption], "Disable CppVerify contract syntax">>;
23762379

2377-
// CppVerify SMT backend (on by default when -fverify-contracts is used).
2378-
defm verify : BoolFOption<"verify",
2379-
FrontendOpts<"RunCppVerify">, DefaultTrue,
2380-
PosFlag<SetTrue, [], [ClangOption, CC1Option],
2381-
"Run CppVerify SMT verification alongside compilation">,
2382-
NegFlag<SetFalse, [], [ClangOption, CC1Option],
2383-
"Skip CppVerify SMT verification (contract syntax and codegen only)">>;
2380+
// Skip the CppVerify SMT step. Only the negative is exposed: the verifier runs by
2381+
// default once contracts are enabled, and -fno-verify turns compilation into a
2382+
// syntax/semantics-only "light check". It implies -fverify-contracts (handled in
2383+
// the driver) because it is meaningless without the contract language.
2384+
def fno_verify : Flag<["-"], "fno-verify">, Group<f_clang_Group>,
2385+
Visibility<[ClangOption, CC1Option]>,
2386+
HelpText<"Check contract syntax and semantics but skip CppVerify SMT verification "
2387+
"(implies -fverify-contracts)">,
2388+
MarshallingInfoNegativeFlag<FrontendOpts<"RunCppVerify">>;
23842389
def cxx_static_destructors_EQ : Joined<["-"], "fc++-static-destructors=">, Group<f_Group>,
23852390
HelpText<"Controls which variables C++ static destructors are registered for">,
23862391
Values<"all,thread-local,none">,

‎clang/include/clang/Parse/Parser.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7466,6 +7466,7 @@ class Parser : public CodeCompletionHandler {
74667466
/// Parse reveal_with_fuel(fn, depth);
74677467
StmtResult ParseRevealWithFuel();
74687468
StmtResult ParseHideSpec();
7469+
void ParseTypeInvariant(Decl *TagDecl);
74697470
StmtResult ParseRevealSpec();
74707471

74717472
/// Parse forall(binder, lo, hi, body) or exists(binder, lo, hi, body).

‎clang/include/clang/Sema/Sema.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10281,6 +10281,9 @@ class Sema final : public SemaBase {
1028110281
/// each contract condition expression. (SemaContract.cpp)
1028210282
ExprResult ActOnContractCondition(ExprResult E);
1028310283

10284+
/// Rewrite unqualified field names to this->field for type_invariant(expr).
10285+
ExprResult ActOnTypeInvariantExpr(ExprResult E, CXXRecordDecl *Record);
10286+
1028410287
/// PerformContextuallyConvertToObjCPointer - Perform a contextual
1028510288
/// conversion of the expression From to an Objective-C pointer type.
1028610289
/// Returns a valid but null ExprResult if no conversion sequence exists.

‎clang/lib/AST/ASTContext.cpp‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3121,6 +3121,22 @@ const LoopContractInfo *ASTContext::getLoopContract(const Stmt *S) const {
31213121
return I != LoopContracts.end() ? I->second : nullptr;
31223122
}
31233123

3124+
TypeContractInfo &ASTContext::getOrCreateTypeContract(const RecordDecl *RD) {
3125+
assert(RD && "Passed null record");
3126+
const RecordDecl *Key = cast<RecordDecl>(RD->getCanonicalDecl());
3127+
auto &Info = TypeContracts[Key];
3128+
if (!Info)
3129+
Info = new (*this) TypeContractInfo();
3130+
return *Info;
3131+
}
3132+
3133+
const TypeContractInfo *ASTContext::getTypeContract(const RecordDecl *RD) const {
3134+
if (!RD)
3135+
return nullptr;
3136+
auto I = TypeContracts.find(cast<RecordDecl>(RD->getCanonicalDecl()));
3137+
return I != TypeContracts.end() ? I->second : nullptr;
3138+
}
3139+
31243140
TypeSourceInfo *ASTContext::CreateTypeSourceInfo(QualType T,
31253141
unsigned DataSize) const {
31263142
if (!DataSize)

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

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

6368+
// CppVerify contract language (last-wins between -f/-fno-verify-contracts).
63686369
Args.addOptInFlag(CmdArgs, options::OPT_fverify_contracts,
63696370
options::OPT_fno_verify_contracts);
6370-
Args.addOptOutFlag(CmdArgs, options::OPT_fverify, options::OPT_fno_verify);
6371+
// -fno-verify skips the SMT step. It is meaningless without the contract
6372+
// language, so when neither -f/-fno-verify-contracts was given it implies
6373+
// -fverify-contracts, yielding a syntax/semantics-only "light check".
6374+
if (Args.hasArg(options::OPT_fno_verify)) {
6375+
CmdArgs.push_back("-fno-verify");
6376+
if (!Args.hasArg(options::OPT_fverify_contracts) &&
6377+
!Args.hasArg(options::OPT_fno_verify_contracts))
6378+
CmdArgs.push_back("-fverify-contracts");
6379+
}
63716380

63726381
if (Arg *A = Args.getLastArg(options::OPT_fcxx_abi_EQ))
63736382
A->render(Args, CmdArgs);

0 commit comments

Comments
 (0)