How to Write C ++ code with exceptional security

Source: Internet
Author: User
How to Write C ++ code with exceptional security-Linux general technology-Linux programming and kernel information. The following is a detailed description. There are many controversies about exceptions in C ++, but they are often non-factual misunderstandings. Exceptions were once a difficult language feature. Fortunately, with the accumulation of experience in the C ++ community, we already have enough knowledge to easily write exceptional and secure code, in addition, writing exceptional and Secure Code generally does not affect performance.

Is the error code returned? This is an endless topic. You must have heard that an exception is used only when an exception occurs. So what is "real exception "? Before answering this question, let's take a look at the variant principle in programming.

An object is an attribute aggregation method. How can we determine whether the attribute aggregation of an object is in a logically correct state? Through a series of assertions, we can conclude that the attribute aggregation logic of this object is correct or problematic. These assertions are non-variant measures of object attribute aggregation.

We usually perform a non-variant check in function calls. There are three types of non-variant conditions: pre-condition, post-condition, and non-variant. A prerequisite is a logical condition that must be met before a function is called. A prerequisite is a logical condition that must be met after a function is called. A non-variant condition is a condition that must be met during function execution. In our discussion, the non-variant is both a prerequisite and a post condition. The preceding conditions must be met. If the preceding conditions are not met, the program logic is incorrect, and the latter conditions are not necessarily met. Now, we can strictly define the exception condition using the non-variant formula: the condition is met, but the condition cannot be met, that is, the exception condition. An exception is thrown only when an exception occurs.

In the response to when an exception is thrown, the returned value is not excluded and the two are orthogonal. However, from our experience, we can choose between the two. Why? In fact, when we make this kind of choice, it will inevitably mean that the interface semantics changes. without changing the interface, it is actually impossible to choose (give it a try, use the return value to handle errors in the constructor ). The normal and abnormal conditions can be distinguished without changing the pattern, and the interface can be further refined.

The evaluation of exceptional security can be divided into three levels: basic assurance, strong assurance, and no failure.

Basic Guarantee: Make sure that the Program (object) is in an unknown but valid State when an exception occurs. Effective means that all the unchanged object checks pass.

Strong guarantee: ensure that the operation is transactional, either successful, the program is in the target State, or does not change.

No failure: This is hard to guarantee for most functions. For C ++ programs, at least the destructor, release function, and swap function must ensure that they do not fail. This is the basis for writing exceptional security code.

First, we start with resource management issues in exceptional circumstances. Many people may have done this:

Type * obj = new Type;
Try {do_something ...}
Catch (...) {delete obj; throw ;}

Don't do this! This will only make your code look messy and reduce efficiency. This is also one of the reasons for the long-standing reputation for exceptions. Please use RAII technology to do this:

Auto_ptr Obj_ptr (new Type );
Do_something...

This code is concise, secure, and cost-effective. When you do not care about or cannot handle exceptions, do not try to catch them. You do not need to use try... catch to write exception-safe code. Most of the exception-safe code does not need try... catch. I admit that the real world is not always as simple as the above example, but this example can indeed represent a lot of abnormal security code practices. In this example, boost: scoped_ptr is a more suitable alternative to auto_ptr.

Let's consider this constructor:

Type (): m_a (new TypeA), m_ B (new TypeB ){}

Assume that the member variables m_a and m_ B are of the original pointer Type and are consistent with the declarative sequence in the Type. Such a code is insecure, and there is a resource leakage problem. The failure rollback mechanism of the constructor cannot cope with this problem. If the new TypeB throws an exception, the resources returned by the new TypeA cannot be released. In the past, many people used this method to avoid exceptions:

Type (): m_a (NULL), m_ B (NULL ){
Auto_ptr Tmp_a (new TypeA );
Auto_ptr Tmp_ B (new TypeB );
M_a = tmp_a.release ();
M_ B = tmp_ B .release ();
}

Of course, such a method can indeed implement code with exceptional security, and the implementation idea is very important. This idea will be used in how to implement the exception security code with strong guarantee. however, this approach is not thorough enough. At least the Destructor should be done manually. We can still use RAII technology to make it more thorough: shared_ptr M_a; shared_ptr M_ B; in this way, we can easily write exceptional security code:

Type (): m_a (new TypeA), m_ B (new TypeB ){}

If you think the performance of shared_ptr cannot meet the requirements, you can write an intelligent pointer class with an interface similar to scoped_ptr and release resources in the destructor. If the class is designed to be non-reproducible, scoped_ptr can also be used directly. We strongly recommend that you do not use auto_ptr as a data member. Although scoped_ptr is not a good name, it is at least safe and will not cause confusion.

RAII technology is not only used in the above example. All operations that must appear in pairs can be completed through this technology without the need to try... catch. The following code is also common:
A_lock.lock ();

Try {...} catch (...) {a_lock.unlock (); throw ;}
A_lock.unlock ();

We can solve this problem by providing a pair of helper classes:

Struct scoped_lock {
Explicit scoped_lock (Lock & lock): m_l (lock) {m_l.lock ();}
~ Scoped_lock () {m_l.unlock ();}
Private:
Lock & m_l;
};

Then, write the code as follows:

Scoped_lock guard (a_lock );
Do_something...

Clear and elegant! In this example, we do not need to operate in pairs. Obviously, the problem can be solved by modifying the scoped_lock constructor. However, method names and parameters are often not so fixed. What should I do? You can use this helper class:

Template
Struct pair_guard {
Pair_guard (FEnd fe, FBegin fb): m_fe (fe) {if (fb) fb ();}
~ Pair_guard () {m_fe ();}
Private:
FEnd m_fe;
... // Prohibit Replication
};
Typedef pair_guard , Function > Simple_pair_guard;

Okay. With the boost library, we can write the code like this:

Simple_pair_guard guard (bind (& Lock: unlock, a_lock), bind (& Lock: lock, a_lock ));
Do_something...

I admit that such code is not as concise and easy to understand as it is before, but it is more flexible and can be used for pairing no matter what the function name is. We can enhance the use of bind, combined with placeholders and reference_wrapper, to process function parameters and dynamically bind variables. All the same work inside and outside the catch is done by pair_guard.
Looking at the previous examples, you may have discovered that the so-called exceptional security code is actually how to avoid try... catch code, which seems contrary to intuition. Sometimes things are so contrary to intuition. Exceptions are everywhere. When you do not need to care about exceptions or cannot handle exceptions, you should avoid capturing exceptions. Unless you want to capture all exceptions, You must throw them again. Try... although the catch method can write code with exceptional security, such code is intolerable in terms of clarity and efficiency, and this is why many people criticize the C ++ exception. In the C ++ world, we should follow the C ++ rules.

If we follow the above principles, can we achieve basic guarantees? To be honest, the infrastructure is ready, but the skills are not enough. Let's continue with the analysis.

For a normal execution process of a method, we may need to modify the object state multiple times within the method. during the execution of the method, the object may be in an invalid state! = Unknown status). If an exception occurs at this time, the object becomes invalid. Using the aforementioned methods, it is feasible to fix objects in pair_guard's destructor, but the code will become complicated due to lack of efficiency. The best way to do this is to avoid it. It is a bit unreasonable, but it is not without reason. When an object is in an invalid state, it means that the object cannot be securely retransmitted or shared at this moment. The practical practice is:

A. Every time you modify an object, make sure that the object is in a valid state.

B. When the object is in an invalid state, all operations will never fail.

In the next strong assurance discussion, I will elaborate on how to achieve these two points.

Strong guarantee is transactional, which is different from the transactional nature of the database, and also has a common nature. The principle of achieving strong guarantee is to calculate the target State of an object in the Process of possible failure, but replace the object with the target State in the process of never failure without modifying the object. Evaluate the knowledge of an insecure string Assignment Method:

String & operator = (const string & rsh ){
If (this! = & Rsh ){
Myalloc locked_pool (m_data );
Locked_pool.deallocate (m_data );
If (rsh. empty ())
M_data = NULL;
Else {
M_data = locked_pool.allocate (rsh. size () + 1 );
Never_failed_copy (m_data, rsh. m_data, rsh. size () + 1 );
}
}
Return * this;
}

Locked_pool is used to lock the Memory Page. For the sake of simplicity, we assume that only the locked_pool constructor and allocate may throw an exception, so this code is not even guaranteed. If allocate fails, the m_data value is invalid. Refer to the above B entry, we can modify the Code as follows:

Myalloc locked_pool (m_data );
Locked_pool.deallocate (m_data); // The status is invalid.
M_data = NULL; // immediately return to the valid state without failure
If (! Rsh. empty ()){
M_data = locked_pool.allocate (rsh. size () + 1 );
Never_failed_memcopy (m_data, rsh. m_data, rsh. size () + 1 );
}

Now, if locked_pool fails, the object will not change. If allocate fails, the object is an empty string. This is neither an initial state nor an expected target State, but it is a legal state. We have clarified the skills required to implement basic assurance. Combined with the aforementioned infrastructure (RAII application), we can fully implement basic assurance... oh, in fact, there are still some omissions, but leave it to the end.

Make the above Code implement a strong guarantee:

Myalloc locked_pool (m_data );
Char * tmp = NULL;
If (! Rsh. empty ()){
Tmp = locked_pool.allocate (rsh. size () + 1 );
Never_failed_memcopy (tmp, rsh. m_data, rsh. size () + 1); // the target State is displayed.
}
Swap (tmp, m_data); // The Object Security enters the target State.
M_alloc.deallocate (tmp); // release the original resource

The code with strong guarantee uses a local variable tmp, first calculates the target State in tmp, and then enters the target State in security. In this process, nothing is lost (the code is clear, performance ). It seems that implementing strong assurance is not much more difficult than basic assurance. In general, it is also true. However, don't be too confident. A typical example is that it is difficult to implement strong guarantee, and a strong guarantee for Interval operations:

For (itr = range. begin (); itr! = Range. end (); ++ itr ){
Itr-> do_something ();
}

If a do_something fails, what status will range be in? This code still provides a basic guarantee, but not a strong guarantee. Based on the basic principle of strong guarantee, we can do this:

Tmp = range;
For (itr = tmp. begin (); itr! = Tmp. end (); ++ itr ){
Itr-> do_something ();
}
Swap (tmp, range );

It seems very simple! This is not undesirable, but sometimes it doesn't work. Because we have paid extra performance costs, and this price may be very high. In any case, we have explained how to implement strong assurance and how to choose between them is up to you. Next we will discuss the last exception security guarantee: no failure.

Generally, we do not need such a strong security guarantee, but we must at least ensure that three types of processes will not fail: destructor, release class functions, and swap. The Structure Analysis and function release will not fail. This is the cornerstone of RAII technology, and swap will not fail to "Replace the object with the target State in the process of never failing ". All of the above discussions are based on the fact that these three processes will not fail. Here, we make up for the omission above.

Generally, assignment, address fetch, and other operations within the language are not subject to exceptions, and the above three types of processes are not subject to exceptions logically. In internal operations, Division operations may throw an exception. However, an address access error is usually an error, rather than an exception. We should have found this in the previous condition check. All simple operations that do not cause exceptions, but still do not cause exceptions. Now we can summarize several guidelines for writing exceptional security code:

1. Throw an exception only when exceptions should be used.

2. If you do not know how to handle exceptions, do not capture (intercept) exceptions.

3. Full Use of RAII and bypass exceptions.

4. Strive to achieve strong assurance, at least basic assurance.

5. Make sure that the destructor, release class functions, and swap do not fail.

In addition, there are some language details, as they are also listed in the topic:

1. Do not throw an exception like this: throw new exception; this will cause memory leakage.

2. Custom type. The reference type of the exception should be caught: catch (exception & e) or catch (const exception & e ).

3. Do not use exception standards, even if it is null. The compiler does not guarantee that only exceptions permitted by the exception specification are thrown. For more information, see related books.
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.