Objects in general should — and security-critical objects must — be maintained in a consistent state even when exceptional conditions arise. Common techniques for maintaining object consistency include
- Input validation (on method parametersarguments, for example)
- Reordering logic so that code that can result in the exceptional condition executes before the object is modified
- Using rollbacks in the event of failure
- Performing required operations on a temporary copy of the object and committing changes to the original object only after their successful completion
- Avoiding the need to modify the object at all
...
Consider the case where the weight
is more than 20 units (21 units in this case). This causes an IllegalArgumentException
, which is intercepted by the custom error reporter. While the logic restores the object's original state in the absence of this exception, the rollback code fails to execute in the event of an exception. Consequently, subsequent invocations of getVolumePackage()
produce incorrect results.
Code Block | ||
---|---|---|
| ||
class Dimensions {
private int length;
private int width;
private int height;
static public final int PADDING = 2;
static public final int MAX_DIMENSION = 10;
public Dimensions(int length, int width, int height) {
this.length = length;
this.width = width;
this.height = height;
}
protected int getVolumePackage(int weight) {
length += PADDING;
width += PADDING;
height += PADDING;
try {
if (length <= PADDING || width <= PADDING || height <= PADDING ||
length > MAX_DIMENSION + PADDING || width > MAX_DIMENSION + PADDING ||
height > MAX_DIMENSION + PADDING || weight <= 0 || weight > 20) {
throw new IllegalArgumentException();
}
int volume = length * width * height; // 12 * 12 * 12 = 1728
length -= PADDING; width -= PADDING; height -= PADDING; // Revert back
return volume;
} catch (Throwable t) {
MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
return -1; // Non-positive error code
}
}
public static void main(String[] args) {
Dimensions d = new Dimensions(10, 10, 10);
System.out.println(d.getVolumePackage(21)); // Prints -1 (error)
System.out.println(d.getVolumePackage(19)); // Prints 2744 instead of 1728
}
}
|
The catch
clause is permitted by exception EXC14*EXC08-EX0 of rule ERR08-J. Do not catch NullPointerException or any of its ancestors because it serves as a general filter passing exceptions to the MyExceptionReporter
class, which is dedicated to safely reporting exceptions as recommended by rule ERR00-J. Do not suppress or ignore checked exceptions. While this code only throws IllegalArgumentException
, the catch clause is general enough to handle any exception in case the try
block should be modified to throw other exceptions.
...
Code Block | ||
---|---|---|
| ||
// ...
} catch (Throwable t) {
MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
length -= PADDING; width -= PADDING; height -= PADDING; // Revert back
return -1;
}
|
Compliant Solution (finally
Clause)
...
Code Block | ||
---|---|---|
| ||
protected int getVolumePackage(int weight) {
length += PADDING;
width += PADDING;
height += PADDING;
try {
if (length <= PADDING || width <= PADDING || height <= PADDING ||
length > MAX_DIMENSION + PADDING ||
width > MAX_DIMENSION + PADDING ||
height > MAX_DIMENSION + PADDING ||
weight <= 0 || weight > 20) {
throw new IllegalArgumentException();
}
int volume = length * width * height; // 12 * 12 * 12 = 1728
return volume;
} catch (Throwable t) {
MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
return -1; // Non-positive error code
} finally {
// Revert back
length -= PADDING; width -= PADDING; height -= PADDING;
}
}
|
...
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="b316135236d97771-634f1734-4bc64dad-bba38f52-cd47332b0799f174921d0326"><ac:plain-text-body><![CDATA[ | [[Bloch 2008 | AA. Bibliography#Bloch 08]] | Item 64: Strive for failure atomicity | ]]></ac:plain-text-body></ac:structured-macro> |
...