Skip to content

Commit 97ba0be

Browse files
committed
Add all unchecked_* operations corresponding to -no-*-checks flags
Comparisons: unchecked_less, unchecked_less_eq, unchecked_greater, and unchecked_greater_eq Division by zero: unchecked_div Dereference: unchecked_dereference Subscript: unchecked_subscript Also fixed a bug in the code that identified attempts to assign to a pointer
1 parent ccf7011 commit 97ba0be

File tree

43 files changed

+343
-118
lines changed

Some content is hidden

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

43 files changed

+343
-118
lines changed

docs/cpp2/safety.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
2+
# Safety and unchecked code
3+
4+
Cpp2 aims to be safe by default, usually entirely at compile time, and when needed at run time.
5+
6+
When Cpp2 rejects unsafe code (e.g., signed/unsigned comparison) or ensuring safety can require run-time checks (e.g., subscripts bounds checks), you can opt out as needed in two ways:
7+
8+
- at a specific place in your code, using `unchecked_*` functions (these are in namespace `cpp2::`, but can be used unqualified from Cpp2 code)
9+
- for a whole source file, using `-no-*-checks` switches
10+
11+
Nearly always, you should opt out at a specific place in your code where you are confident the result is okay, and if there is a run-time check you have measured that the performance difference matters such as in a hot loop.
12+
13+
14+
### <a id="mixed-sign-comparison"></a> Integer mixed-sign `<`, `<=`, `>`, and `>=` (compile-time enforced)
15+
16+
Comparing signed and unsigned integer values directly using `<`, `<=`, `>`, or `>=` can give wrong results, and so such comparisons are rejected at compile time.
17+
18+
To disable this check at a specific place in your code, use the appropriate `unchecked_cmp_*` function instead of the operator notation: `unchecked_cmp_less`, `unchecked_cmp_less_eq`,`unchecked_cmp_greater`, or `unchecked_cmp_greater_eq`.
19+
20+
For example:
21+
22+
``` cpp title="Integer comparisons" hl_lines="7"
23+
main: () = {
24+
x: i32 = 42;
25+
y: u32 = 43;
26+
27+
if x < y { } // unsafe, therefore error by default
28+
29+
if unchecked_cmp_less(x,y) { } // ok, explicit "trust me" opt-out
30+
}
31+
```
32+
33+
To disable these checks for the entire source file, you can use cppfront's `-no-comparison-checks` switch:
34+
35+
``` bash title="Disable prevention of mixed-sign integer comparisons" hl_lines="3"
36+
cppfront myfile.cpp2 # mixed-sign int comparisons banned
37+
38+
cppfront myfile.cpp2 -no-comparison-checks # mixed-sign int comparisons allowed
39+
```
40+
41+
42+
### <a id="division-by-zero"></a> Division by zero (run-time checked)
43+
44+
Dividing integers by zero is undefined behavior, and is rejected at run time by checking the denominator is nonzero.
45+
46+
To disable this check at a specific place in your code, use `unchecked_div`. For example:
47+
48+
``` cpp title="Division by zero" hl_lines="7"
49+
main: () = {
50+
x := 42;
51+
y := 0;
52+
53+
z := x/y; // unsafe, therefore run-time checked
54+
55+
w := unchecked_div(x,y) // ok, explicit "trust me" opt-out
56+
}
57+
```
58+
59+
To disable these checks for the entire source file, you can use cppfront's `-no-div-zero-checks` switch:
60+
61+
``` bash title="Disable prevention of division by zero" hl_lines="3"
62+
cppfront myfile.cpp2 # division by zero checked
63+
64+
cppfront myfile.cpp2 -no-div-zero-checks # division by zero not checked
65+
```
66+
67+
68+
### <a id="null-dereference"></a> Null dereference (run-time checked)
69+
70+
Dereferencing a null pointer is undefined behavior, and is rejected at run time by checking the pointer is nonzero.
71+
72+
To disable this check at a specific place in your code, use `unchecked_dereference`. For example:
73+
74+
``` cpp title="Null dereference" hl_lines="6"
75+
main: () = {
76+
p: *int = cpp1_func(); // could be initialized to null
77+
78+
p* = 42; // unsafe, therefore run-time checked
79+
80+
unchecked_dereference(p) = 42; // ok, explicit "trust me" opt-out
81+
}
82+
```
83+
84+
To disable these checks for the entire source file, you can use cppfront's `-no-null-checks` switch:
85+
86+
``` bash title="Disable prevention of null deference" hl_lines="3"
87+
cppfront myfile.cpp2 # null dereferences checked
88+
89+
cppfront myfile.cpp2 -no-null-checks # null dereferences not checked
90+
```
91+
92+
93+
### <a id="subscript-bounds"></a> Subscript bounds (run-time checked)
94+
95+
Accessing an out of bounds subscript is undefined behavior, and is rejected at run time by checking the subscript is nonzero. For an expression `a[b]` where
96+
97+
- `a` is contiguous and supports `std::size(a)`, and
98+
- `b` is an integral value
99+
100+
the cppfront compiler injects a check that **`0 <= b < std::size(a)`** before the call to `a[b]`.
101+
102+
To disable this check at a specific place in your code, use `unchecked_subscript`. For example:
103+
104+
``` cpp title="Subscript bounds" hl_lines="12 13"
105+
main: () = {
106+
v: std::vector = ( 1, 2, 3, 4, 5 );
107+
s: std::span = v;
108+
109+
idx := calc_index(s);
110+
111+
v[idx] += 42; // unsafe, therefore run-time checked
112+
s[idx] += 84; // unsafe, therefore run-time checked
113+
114+
// manually hoist the check and do it myself
115+
if (0 ..< v.size()).contains(idx) {
116+
unchecked_subscript(v,idx) += 42; // ok, explicit "trust me" opt-out
117+
unchecked_subscript(s,idx) += 84; // ok, explicit "trust me" opt-out
118+
}
119+
}
120+
```
121+
122+
To disable these checks for the entire source file, you can use cppfront's `-no-subscript-checks` switch:
123+
124+
``` bash title="Disable prevention of out-of-bounds subscripts" hl_lines="3"
125+
cppfront myfile.cpp2 # subscript bounds checked
126+
127+
cppfront myfile.cpp2 -no-subscript-checks # subscript bounds not checked
128+
```
129+

include/cpp2util.h

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,7 +2601,7 @@ range(
26012601
) -> range<std::common_type_t<T, U>>;
26022602

26032603
template<typename T>
2604-
constexpr auto contains(range<T> const& r, T const& t)
2604+
constexpr auto contains(range<T> const& r, auto const& t)
26052605
-> bool
26062606
{
26072607
if (r.empty()) {
@@ -2711,6 +2711,64 @@ inline auto fopen( const char* filename, const char* mode ) {
27112711

27122712

27132713

2714+
2715+
//-----------------------------------------------------------------------
2716+
//
2717+
// "Unchecked" opt-outs for safety checks
2718+
//
2719+
//-----------------------------------------------------------------------
2720+
//
2721+
2722+
CPP2_FORCE_INLINE constexpr auto unchecked_cmp_less(auto&& t, auto&& u)
2723+
-> decltype(auto)
2724+
requires requires {CPP2_FORWARD(t) < CPP2_FORWARD(u);}
2725+
{
2726+
return CPP2_FORWARD(t) < CPP2_FORWARD(u);
2727+
}
2728+
2729+
CPP2_FORCE_INLINE constexpr auto unchecked_cmp_less_eq(auto&& t, auto&& u)
2730+
-> decltype(auto)
2731+
requires requires {CPP2_FORWARD(t) <= CPP2_FORWARD(u);}
2732+
{
2733+
return CPP2_FORWARD(t) <= CPP2_FORWARD(u);
2734+
}
2735+
2736+
CPP2_FORCE_INLINE constexpr auto unchecked_cmp_greater(auto&& t, auto&& u)
2737+
-> decltype(auto)
2738+
requires requires {CPP2_FORWARD(t) > CPP2_FORWARD(u);}
2739+
{
2740+
return CPP2_FORWARD(t) > CPP2_FORWARD(u);
2741+
}
2742+
2743+
CPP2_FORCE_INLINE constexpr auto unchecked_cmp_greater_eq(auto&& t, auto&& u)
2744+
-> decltype(auto)
2745+
requires requires {CPP2_FORWARD(t) >= CPP2_FORWARD(u);}
2746+
{
2747+
return CPP2_FORWARD(t) >= CPP2_FORWARD(u);
2748+
}
2749+
2750+
CPP2_FORCE_INLINE constexpr auto unchecked_div(auto&& t, auto&& u)
2751+
-> decltype(auto)
2752+
requires requires {CPP2_FORWARD(t) / CPP2_FORWARD(u);}
2753+
{
2754+
return CPP2_FORWARD(t) / CPP2_FORWARD(u);
2755+
}
2756+
2757+
CPP2_FORCE_INLINE constexpr auto unchecked_dereference(auto&& p)
2758+
-> decltype(auto)
2759+
requires requires {*CPP2_FORWARD(p);}
2760+
{
2761+
return *CPP2_FORWARD(p);
2762+
}
2763+
2764+
CPP2_FORCE_INLINE constexpr auto unchecked_subscript(auto&& a, auto&& b)
2765+
-> decltype(auto)
2766+
requires requires {CPP2_FORWARD(a)[b];}
2767+
{
2768+
return CPP2_FORWARD(a)[b];
2769+
}
2770+
2771+
27142772
namespace impl {
27152773

27162774
//-----------------------------------------------------------------------
@@ -2729,7 +2787,8 @@ CPP2_FORCE_INLINE constexpr auto cmp_mixed_signedness_check() -> void
27292787
{
27302788
static_assert(
27312789
program_violates_type_safety_guarantee<T, U>,
2732-
"comparing bool values using < <= >= > is unsafe and not allowed - are you missing parentheses?");
2790+
"comparing bool values using < <= >= > is unsafe and not allowed - are you missing parentheses?"
2791+
);
27332792
}
27342793
else if constexpr (
27352794
std::is_integral_v<T> &&
@@ -2745,20 +2804,22 @@ CPP2_FORCE_INLINE constexpr auto cmp_mixed_signedness_check() -> void
27452804
// static_assert to reject the comparison is the right way to go.
27462805
static_assert(
27472806
program_violates_type_safety_guarantee<T, U>,
2748-
"mixed signed/unsigned comparison is unsafe - prefer using .ssize() instead of .size(), consider using std::cmp_less instead, or consider explicitly casting one of the values to change signedness by using 'as' or 'cpp2::unchecked_narrow'"
2807+
"mixed signed/unsigned comparison is unsafe - prefer using .ssize() instead of .size(), consider using std::cmp_less or similar instead, or consider explicitly casting one of the values to change signedness by using 'as' or 'cpp2::unchecked_narrow'"
27492808
);
27502809
}
27512810
}
27522811

27532812

2754-
CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) -> decltype(auto)
2813+
CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u)
2814+
-> decltype(auto)
27552815
requires requires {CPP2_FORWARD(t) < CPP2_FORWARD(u);}
27562816
{
27572817
cmp_mixed_signedness_check<CPP2_TYPEOF(t), CPP2_TYPEOF(u)>();
27582818
return CPP2_FORWARD(t) < CPP2_FORWARD(u);
27592819
}
27602820

2761-
CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) -> decltype(auto)
2821+
CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u)
2822+
-> decltype(auto)
27622823
{
27632824
static_assert(
27642825
program_violates_type_safety_guarantee<decltype(t), decltype(u)>,
@@ -2768,14 +2829,16 @@ CPP2_FORCE_INLINE constexpr auto cmp_less(auto&& t, auto&& u) -> decltype(auto)
27682829
}
27692830

27702831

2771-
CPP2_FORCE_INLINE constexpr auto cmp_less_eq(auto&& t, auto&& u) -> decltype(auto)
2832+
CPP2_FORCE_INLINE constexpr auto cmp_less_eq(auto&& t, auto&& u)
2833+
-> decltype(auto)
27722834
requires requires {CPP2_FORWARD(t) <= CPP2_FORWARD(u);}
27732835
{
27742836
cmp_mixed_signedness_check<CPP2_TYPEOF(t), CPP2_TYPEOF(u)>();
27752837
return CPP2_FORWARD(t) <= CPP2_FORWARD(u);
27762838
}
27772839

2778-
CPP2_FORCE_INLINE constexpr auto cmp_less_eq(auto&& t, auto&& u) -> decltype(auto)
2840+
CPP2_FORCE_INLINE constexpr auto cmp_less_eq(auto&& t, auto&& u)
2841+
-> decltype(auto)
27792842
{
27802843
static_assert(
27812844
program_violates_type_safety_guarantee<decltype(t), decltype(u)>,
@@ -2785,14 +2848,16 @@ CPP2_FORCE_INLINE constexpr auto cmp_less_eq(auto&& t, auto&& u) -> decltype(aut
27852848
}
27862849

27872850

2788-
CPP2_FORCE_INLINE constexpr auto cmp_greater(auto&& t, auto&& u) -> decltype(auto)
2851+
CPP2_FORCE_INLINE constexpr auto cmp_greater(auto&& t, auto&& u)
2852+
-> decltype(auto)
27892853
requires requires {CPP2_FORWARD(t) > CPP2_FORWARD(u);}
27902854
{
27912855
cmp_mixed_signedness_check<CPP2_TYPEOF(t), CPP2_TYPEOF(u)>();
27922856
return CPP2_FORWARD(t) > CPP2_FORWARD(u);
27932857
}
27942858

2795-
CPP2_FORCE_INLINE constexpr auto cmp_greater(auto&& t, auto&& u) -> decltype(auto)
2859+
CPP2_FORCE_INLINE constexpr auto cmp_greater(auto&& t, auto&& u)
2860+
-> decltype(auto)
27962861
{
27972862
static_assert(
27982863
program_violates_type_safety_guarantee<decltype(t), decltype(u)>,
@@ -2802,14 +2867,16 @@ CPP2_FORCE_INLINE constexpr auto cmp_greater(auto&& t, auto&& u) -> decltype(aut
28022867
}
28032868

28042869

2805-
CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) -> decltype(auto)
2870+
CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u)
2871+
-> decltype(auto)
28062872
requires requires {CPP2_FORWARD(t) >= CPP2_FORWARD(u);}
28072873
{
28082874
cmp_mixed_signedness_check<CPP2_TYPEOF(t), CPP2_TYPEOF(u)>();
28092875
return CPP2_FORWARD(t) >= CPP2_FORWARD(u);
28102876
}
28112877

2812-
CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u) -> decltype(auto)
2878+
CPP2_FORCE_INLINE constexpr auto cmp_greater_eq(auto&& t, auto&& u)
2879+
-> decltype(auto)
28132880
{
28142881
static_assert(
28152882
program_violates_type_safety_guarantee<decltype(t), decltype(u)>,

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ nav:
6464
- 'Types and inheritance': cpp2/types.md
6565
- 'Metafunctions and reflection': cpp2/metafunctions.md
6666
- 'Namespaces': cpp2/namespaces.md
67+
- 'Safety and "unchecked"': cpp2/safety.md
6768
# - 'Modules': cpp2/modules.md
6869
- 'Cppfront reference':
6970
- 'Using Cpp1 (today''s syntax) and Cpp2 in the same source file': cppfront/mixed.md

regression-tests/mixed-bounds-safety-with-assert-2.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ main: () -> int = {
77
std::cout << i << "\n";
88
}
99

10-
add_42_to_subrange: (inout rng:_, start:int, end:int)
10+
add_42_to_subrange: (inout rng, start:int, end:int)
1111
= {
1212
assert<bounds_safety>( 0 <= start );
1313
assert<bounds_safety>( end <= rng.ssize() );

regression-tests/mixed-bounds-safety-with-assert.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ main: () -> int = {
66
print_subrange(v, 1, 13);
77
}
88

9-
print_subrange: (rng:_, start:int, end:int) = {
9+
print_subrange: (rng, start:int, end:int) = {
1010
assert<bounds_safety>( 0 <= start );
1111
assert<bounds_safety>( end <= rng.ssize() );
1212

regression-tests/mixed-fixed-type-aliases.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace my {
66
using u16 = float;
77
}
88

9-
test: (x:_) = {
9+
test: (x) = {
1010
std::cout
1111
<< std::is_floating_point_v<CPP2_TYPEOF(x)> as std::string
1212
<< "\n";

regression-tests/mixed-initialization-safety-1-error.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ main: () -> int =
3232

3333
// Print! A one-expression function body...
3434
//
35-
print_decorated: (x:_) = std::cout << ">> [" << x << "]\n";
35+
print_decorated: (x) = std::cout << ">> [" << x << "]\n";
3636

3737
// Flip a coin! Exercise <mutex> <cstdlib> <ctime> and 'as'...
3838
//

regression-tests/mixed-initialization-safety-2-error.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ main: () -> int =
3232

3333
// Print! A one-expression function body...
3434
//
35-
print_decorated: (x:_) = std::cout << ">> [" << x << "]\n";
35+
print_decorated: (x) = std::cout << ">> [" << x << "]\n";
3636

3737
// Flip a coin! Exercise <mutex> <cstdlib> <ctime> and 'as'...
3838
//

regression-tests/mixed-initialization-safety-3-contract-violation.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fill: (
2525
x = value.substr(0, count);
2626
}
2727

28-
print_decorated: (x:_) = {
28+
print_decorated: (x) = {
2929
std::cout << ">> [" << x << "]\n";
3030
}
3131

regression-tests/mixed-initialization-safety-3.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fill: (
2323
x = value.substr(0, count);
2424
}
2525

26-
print_decorated: (x:_) = {
26+
print_decorated: (x) = {
2727
std::cout << ">> [" << x << "]\n";
2828
}
2929

regression-tests/mixed-inspect-values.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ main: ()->int = {
2828
test(3.14);
2929
}
3030

31-
test: (x:_) = {
31+
test: (x) = {
3232
forty_two := 42;
3333
std::cout << inspect x -> std::string {
3434
is 0 = "zero";

regression-tests/mixed-intro-example-three-loops.cpp2

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
#include <span>
66
#include <memory>
77

8-
auto print(auto const& thing) -> void {
9-
std::cout << ">> " << thing << "\n";
8+
auto print(auto const& x) -> void {
9+
std::cout << ">> " << x << "\n";
1010
}
1111

12-
auto decorate_and_print(auto& thing) -> void {
13-
thing = "[" + thing + "]";
14-
print(thing);
12+
auto decorate_and_print(auto& x) -> void {
13+
x = "[" + x + "]";
14+
print(x);
1515
}
1616

1717
auto main() -> int {

0 commit comments

Comments
 (0)