Some programming language constructs and programming techniques are inherently error-prone and so should be avoided or, at least, used as little as possible. Potentially error-prone constructs include:
- Unconditional branch (goto) statements The dangers of goto statements were recognized as long ago as 1968 (Dijkstra, 1968) and, as a consequence, these have been excluded from modern programming languages. However, they are still allowed in languages such as C. The use of goto statements leads to ‘spaghetti code’ that is tangled, difficult to understand and debug.
- Floating-point numbers The representation of floating point numbers in a fixed length memory word is inherently imprecise. This is a particular problem when numbers are compared because representation imprecision may lead to invalid comparisons. For example, 3.00000000 may sometimes be represented as 2.99999999 and sometimes as 3.00000001. A comparison would show these to be unequal. Fixed-point numbers where a number is represented to a given number of decimal places are safer because exact comparisons are possible.
- Pointers Programming languages such as C and C++ support low-level constructs called pointers, which hold addresses that refer directly to areas of the machine memory (they point to a memory location). Errors in the use of pointers can be devastating, if they are set incorrectly and so point to the wrong area of memory. They also make bound checking of arrays and other structures harder to implement.
- Dynamic memory allocation Program memory may be allocated at run-time rather than at compile-time. The danger with this is that the memory may not be properly de-allocated, so eventually the system runs out of available memory. This can be a very difficult error to detect because the system may run successfully for a long time before the problem occurs.
- Parallelism When processes are executing concurrently, there can be subtle timing dependencies between them. Timing problems cannot usually be detected by program inspection and the peculiar combination of circumstances that cause a timing problem may not occur during system testing. Parallelism may be unavoidable, but its use should be carefully controlled to minimize inter-process dependencies.
- Recursion When a procedure or method calls itself or calls another procedure, which then calls the original calling procedure, this is ‘recursion’. The use of recursion can result in concise programs but it can be difficult to follow the logic of recursive programs. Programming errors are therefore more difficult to detect. Recursion errors may result in the allocation of all the system’s memory as temporary stack variables are created.
- Interrupts These are a means of forcing control to transfer to a section of code irrespective of the code currently executing. The dangers of this are obvious; the interrupt may cause a critical operation to be terminated.
- Inheritance The problem with inheritance in object-oriented programming is that the code associated with an object is not all in one place. This makes it more difficult to understand the behaviour of the object. Hence, it is more likely that programming errors will be missed. Furthermore, inheritance when combined with dynamic binding can cause timing problems at run-time. Different instances of a method may be bound to a call, depending on the parameter types. Consequently, different amounts of time will be spent searching for the correct method instance.
- Aliasing This occurs when more than one name is used to refer to the same entity in a program. For example, if two pointers with different names point to the same memory location. It is easy for program readers to miss statements that change the entity when they have several names to consider.
- Unbounded arrays In languages like C, arrays are simply ways of accessing memory and you can make assignments beyond the end of an array. The run-time system does not check that assignments actually refer to elements in the array. Buffer overflow, where an attacker deliberately constructs a program to write memory beyond the end of a buffer that is implemented as an array, is a known security vulnerability.
- Default input processing Some systems provide a default for input processing irrespective of the input that is presented to the system. This is a security loophole that an attacker may exploit by presenting the program with unexpected inputs that are not rejected by the system.