Several well-known adages in object-oriented design suggest that the dependency structure of a package or module must never contain cycles; that is, it must be representable as a directed acyclic graph (DAG).
There are several advantages of eliminating cycles between packages:
* Testing and maintainability: Cyclic dependencies magnify the repercussions of changes or patches to source code. Reducing the repercussions of changes eases testing and improves maintainability. Inability to perform adequate testing because of cyclic dependencies is a frequent source of security vulnerabilities.
* Reusability: Cyclic dependencies between packages require that the packages be released and upgraded in lock-step. This requirement reduces reusability.
* Releases and builds: Avoiding cycles also helps to steer the development towards an environment that fosters modularization.
* Deployment: Avoiding cyclic dependencies between packages reduces coupling between packages. Reduced coupling reduces the frequency of runtime errors such as {{ClassNotFoundError}}. This, in turn, simplifies deployment.
h2. Noncompliant Code Example
This noncompliant code example contains packages named {{Account}} and {{User}} that consist of the classes {{AccountHolder}} and {{UserDetails}}, respectively. The class {{UserDetails}} extends from {{AccountHolder}} because a user is a kind of account holder. The class {{AccountHolder}} depends on a few non-static utility methods defined in {{UserDetails}} and must declare and use its instance. Likewise, the {{UserDetails}} depends on {{AccountHolder}} but instead chooses to extend from it.
{mc} Methods in AccountHolder are designed for inheritance and should not be final {mc}
{code:bgColor=#ffcccc}
package Account;
import User.*;
public class AccountHolder {
private UserDetails ud; // Uses a class defined in package User
synchronized void depositFunds(String username, double amount) {
// Use a utility method of UserDetails to check whether username exists
if (ud.exists(username)) {
// Deposit the amount
}
}
protected double getBalance(String accountNumber) {
// return the account balance
return 1.0;
}
}
}code}
{code:bgColor=#ffcccc}
package User;
import Account.*;
public class UserDetails extends AccountHolder {
public synchronized double getUserBalance(String accountNumber) {
// Use a method of AccountHolder to get the account balance
return getBalance(accountNumber);
}
public boolean exists(String username) {
// Check whether user exists
return true; // Exists
}
}
{code}
h2. Compliant Solution
The tight coupling between the classes in the two packages can be weakened by introducing an interface called {{BankApplication}} in a third package, {{Bank}}. The cyclic dependency is eliminated by ensuring that the {{AccountHolder}} does not use an instance of {{UserDetails}}, but instead relies on the interface by importing the {{Bank}} package (and not by implementing the interface).
In this compliant solution, such functionality is achieved by adding a parameter of the interface type {{BankApplication}} to the {{depositFunds()}} method. This gives the {{AccountHolder}} a solid contract to bank upon. Additionally, {{UserDetails}} implements the interface and provides concrete implementations of the methods while at the same time, inheriting the other methods from {{AccountHolder}}.
{code:bgColor=#ccccff}
package Bank;
public interface BankApplication {
void depositFunds(BankApplication ba, String username, double amount);
double getBalance(String accountNumber);
double getUserBalance(String accountNumber);
boolean exists(String username);
}
{code}
{code:bgColor=#ccccff}
package Account;
import Bank.*; // Import from a third package
class AccountHolder {
public synchronized void depositFunds(BankApplication ba, String username, double amount) {
// Use a utility method of UserDetails to check whether username exists
if (ba.exists(username)) {
// Deposit the amount
}
}
public double getBalance(String accountNumber) {
// Return the account balance
return 1.0;
}
}
{code}
{code:bgColor=#ccccff}
package User;
import Account.*; // One way dependency
import Bank.*; // Import from a third package
public class UserDetails extends AccountHolder implements BankApplication {
public synchronized double getUserBalance(String accountNumber) {
// Use a method of AccountHolder to get the account balance
return getBalance(accountNumber);
}
public boolean exists(String username) {
// Check whether user exists
return true;
}
}
{code}
{code:bgColor=#ccccff}
package Implementer;
import Bank.*;
import Account.*;
import User.*;
class BankOperations {
private BankApplication ba;
public BankOperations(BankApplication ba) {
this.ba = ba;
}
public void doUserActions() {
System.out.println(ba.exists("user"));
System.out.println(ba.getUserBalance("1111"));
}
public static void main(String[] args) {
AccountHolder ac = new AccountHolder();
ac.depositFunds(new UserDetails(), "user", 1.0); // Pass an interface argument
BankOperations bo = new BankOperations(new UserDetails());
bo.doUserActions();
}
}
{code}
The interface {{BankApplication}} appears to contain superfluous methods such as {{depositFunds()}} and {{getBalance()}}. These methods are present so that if the subclass overrides them, the superclass retains the capability of internally invoking the subclass' methods polymorphically (for example calling {{ba.getBalance()}}, with an overridden implementation of the method in {{UserDetails}}). One consequence of using this implementation is that methods declared in the interface are required to be public in the classes that define them.
h2. Risk Assessment
Cyclic dependencies between packages can result in fragile builds. A security vulnerability in a package can easily percolate to other packages.
|| Guideline || Severity || Likelihood || Remediation Cost || Priority || Level ||
| DCL13-J | low | probable | medium | {color:green}{*}P4{*}{color} | {color:green}{*}L3{*}{color} |
h3. Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this guideline on the [CERT website|https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+DCL13-J].
h2. Bibliography
\[[Martin 1996|AA. Bibliography#Martin 96]\]
\[[Knoernschild 2001|AA. Bibliography#Knoernschild 01]\] Chapter 1: "OO Principles and Patterns, 1.2.5 Acyclic Dependencies Principle"
----
[!The CERT Oracle Secure Coding Standard for Java^button_arrow_left.png!|DCL12-J. Prevent class initialization cycles] [!The CERT Oracle Secure Coding Standard for Java^button_arrow_up.png!|Miscellaneous (MSC)] [!The CERT Oracle Secure Coding Standard for Java^button_arrow_right.png!|MSC09-J. Carefully design interfaces before releasing them]
|