-
Notifications
You must be signed in to change notification settings - Fork 261
[SUGGESTION][FIX] Add safety check support for cpp1 pointers and deduced from other cpp2 declarations #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
include/cpp2util.h
Outdated
#define CPP2_SAFETYCHECK(x) []<typename T>(T&& o) { \ | ||
if constexpr (std::is_pointer_v<std::remove_cvref_t<T>>) { \ | ||
return cpp2::safetychecked_pointer(o); \ | ||
} else { \ | ||
return std::forward<T>(o); \ | ||
} \ | ||
}(x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think cppfront does this elsewhere. But since you're using a macro anyways, I think it'd be better to rely on guaranteed copy elision and return x
directly. There are types for which this current implementation will have bad performance, like big aggregates or those for which move is copy.
#define CPP2_SAFETYCHECK(x) []<typename T>(T&& o) { \ | |
if constexpr (std::is_pointer_v<std::remove_cvref_t<T>>) { \ | |
return cpp2::safetychecked_pointer(o); \ | |
} else { \ | |
return std::forward<T>(o); \ | |
} \ | |
}(x) | |
#define CPP2_SAFETYCHECK(x) []() { \ | |
if constexpr (std::is_pointer_v<std::remove_cvref_t<decltype(x)>>) { \ | |
return cpp2::safetychecked_pointer(x); \ | |
} else { \ | |
return x; \ | |
} \ | |
}() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to pass x to the lambda somehow - your proposed code probably will not work as x
is not a global (need to check that). What I did was not use moving but perfect forwarding - that is why the lambda is a template.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, I forgot [&]
. Would that suffice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, that has problems with bit-fields, and structured bindings in C++20.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I did was not use moving but perfect forwarding - that is why the lambda is a template.
Yes, but that'll compile to a copy for big PODs. Actually a move, but for big PODs, there's no difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I will test that. If you can think about test cases I can verify them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think something like this could work without tripping due to big PODs, bit-fields and structured bindings: https://godbolt.org/z/bovP3vfvo.
template<class U>
using safetychecked_pointer_impl = decltype([]<typename T>(T&& o) {
if constexpr (std::is_pointer_v<T>) {
return safetychecked_pointer(o);
} else {
return std::forward<T>(o);
}
}(std::declval<U>()));
#define CPP2_SAFETYCHECK(x) safetychecked_pointer_impl<decltype((x))>(x)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This macro will be used only in declaring variables - to help deduce the type and introduce safety checks. So probably there will be no issue with coping as there will be coping in the end. But current implementation still has issues with assignments to reference.
std::array<double, 64> tmp {1,2,3,4,5,6,7,8,9,0};
void fun_ref() {
auto& a = CPP2_SAFETYCHECK(tmp);
}
will end up with an error:
error: cannot bind non-const lvalue reference of type 'std::array<double, 64>&' to an rvalue of type 'std::array<double, 64>'
90 | #define CPP2_SAFETYCHECK(x) []<typename T>(T&& o) { \
| ~~~~~~~~~~~~~~~~~~~~~~~~~
91 | if constexpr (std::is_pointer_v<T>) { \
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
92 | return safetychecked_pointer(o); \
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
93 | } else { \
| ~~~~~~~~~~
94 | return std::forward<T>(o); \
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
95 | } \
| ~~~
96 | }(x)
| ~^~~
<source>:101:15: note: in expansion of macro 'CPP2_SAFETYCHECK'
101 | auto& a = CPP2_SAFETYCHECK(tmp);
That can be solved by using decltype(auto)
:
#define CPP2_SAFETYCHECK(x) []<typename T>(T&& o) -> decltype(auto) { \
if constexpr (std::is_pointer_v<T>) { \
return safetychecked_pointer(o); \
} else { \
return std::forward<T>(o); \
} \
}(x)
The working prototype is here: https://godbolt.org/z/reTqKs18h
I will check how it works with the other types you mentioned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There might be no copying in some cases, but when x
is a prvalue, you've initialized o
from x
, before o
is used to initialize the user-declared variable. I think that'll fail for non-movable non-copyable types. My suggestion to fold the return type of the lambda should be able to make the user-declared variable be initialized directly from x
when the type of x
is not a pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, thank you for your feedback. I decided to remove the macro entirely and switch to the is()
/as()
functions style.
Instead of the CPP2_SAFETYCHECK
macro now there are cpp2::safety_check()
functions with overloads that check requirements for types. Probably we can tweak the performance. My goal for now is to make it working for all cases.
I have tested the implementation with bitfields and unique_ptr:
auto up = std::make_unique<int>(42);
void fun_noncopyable() {
auto a = safety_check(up);
auto b = safety_check(std::make_unique<int>(44));
}
struct S {
unsigned int b : 3;
} s{ 3 };
void fun_bitfields() {
auto a = safety_check(s.b);
}
Previous cases still work:
std::array<double, 64> tmp {1,2,3,4,5,6,7,8,9,0};
void fun_ref() {
auto& a = safety_check(tmp);
}
void fun_copy() {
auto a = safety_check(tmp);
}
void fun_rvalue() {
auto a = safety_check(std::array<double, 64>{9,8,7,6,5,4,3,2,1});
}
void fun_ptr() {
auto a = safety_check(&tmp);
}
Working prototype: https://godbolt.org/z/4n5Yraend
Forget to add link to working prototype: https://godbolt.org/z/Gc8K4vnrM |
50e2993
to
401834d
Compare
This change has more issues (e.g. pointers to functions, moving of lvalue, or problem with lack of closing parentheses when initialization happens together with UFCS). I already have a fix. Will push them soon. |
401834d
to
1e99b0d
Compare
The last push solves all issues I have found. Also brings support for deducing pointer types of the cpp2 functions or objects. After this change cppfront will be able to capture safety violations presented below: fun: (inout i : int) -> * const int = {
return i&;
}
main: () -> int = {
i := 42;
p := fun(i);
fun(i)++; // captured
fun(i)--; // captured
fun(i)[0]; // captured
fun(i)~; // captured
p = 0; // captured
p++; // captured
p--; // captured
p[0]; // captured
p~; // captured
} Thanks to better introspection of the cpp2 functions and variables that are known to cppfront (and which return type is explicitly stated). Variables that are initialized using cpp2 variables are known and it is clear to cppfront if it is a pointer or not. That means that fewer functions and variables will be surrounded by Also, function pointers are now secured with
|
While working on this PR I found a bug in the |
1e99b0d
to
6a7c818
Compare
11424f0
to
b4ab3e0
Compare
aafe492
to
a04ffe4
Compare
59aca2c
to
68316dd
Compare
@hsutter in #102 you renamed the function My code looks like this: auto get_declaration_of(token const& t, bool look_beyond_current_function = false) -> declaration_sym const*
{
// skipping some code
if (
decl.declaration->type.index() == declaration_node::function // Don't look beyond the current function
&& !look_beyond_current_function
) {
return nullptr;
}
// skipping some code
) Now, it does not make sense to add such a flag to the |
68316dd
to
7969b7b
Compare
Add regression tests for deduced pointers
7969b7b
to
2cbb57e
Compare
Thanks to relocating pointer_declarators this change introduce deduction of pointer types also from functions - if function or variable is defined in cpp2 then cppfront has all information to deduce the proper type during compilation.
2cbb57e
to
505a088
Compare
See #93 comment... but a note specifically about |
Yes, I know. I was trying to move it. I will make that change. |
Thanks again for this. It's a big PR and it would take me a lot of time to review, and perhaps the implementation will be easier a few months from now that we have a clearer type-id node and soon actual types. So I'm going to close this for now, but if you still feel you want to pursue it perhaps we could revisit this in the summer with fresh PR? Thanks for understanding, and thanks again for all your contributions! |
I was going to close it too. |
@filipsajdak Thanks. Same for #196? |
@hsutter I have ported #196 to the type_id node and simplified the algorithm for deducing pointers. Could you take a look at it? It deduces pointers pretty well, and if it does not collide with your other work, cppfront will benefit from having it working. Of course, if it collides, do not hesitate to postpone it. |
The current implementation has no safety checks for:
This change brings support for these cases and makes the below code fail to compile due to safety check errors:
cppfront will identify which initializations are suspicious and put them into the
cpp2::safety_check()
function. Suspicious initialization is initialization by a function or identifier that cppfront has no information about (i.e. don't know the return type or the variable is from cpp1 and we have no sema information about it).The function checks if it deals with the pointer type. If it is then the pointer is packed into
cpp2::safetychecked_pointer
that hasstatic_asserts
that check if lifetime safety guarantee rules are followed by overloading most operators.The above code will compile to:
When you will try to compile it with the cpp1 compiler you will end with an error:
Not all errors are listed at once - as the compilation process stops on errors. Errors are done in a way that they mimic errors that comes from cppfront:
Update 2022-10-30
After moving pointer decorators to
unqualified_id_node
we can now deduce the types based on a declaration of variables and functions that are used to initialize the variable.Link to previously used working prototypes: