Issue Categories

DeepSource detects issues using static analysis across categories like anti-patterns, bug risks, coverage, performance issues, security flaws, style issues, and type check issues.

Anti-patterns

Anti-patterns are certain ways of writing code that result in poor design. While anti-patterns are correct code, they are not recommended as they often affect maintainability, readability, performance, and security.

Examples of anti-patterns:

In Python, the file class is equipped with special methods that are automatically called whenever a file is opened via a with the statement (e.g. with open("file.txt", "r") as file). These special methods ensure that the file is properly and safely opened and closed.

The code below does not use with to open a file. This code depends on you remembering to manually close the file viaclose() when finished. Even if you remember to call close(), the code is still dangerous, because if an exception occurs before the call to close(), it will not be called and memory issues or file corruption could occur.

f = open('/tmp/.deepsource.toml', 'r')  
f.write("config file.")  
f.close()

It is a good practice to usewith to open a file. The open function is implemented internally as a class, which defines special built-in methods called __enter__() and __exit__(). These methods will automatically open and close the file for you. Python can guarantee that these special methods are always called, even if an exception occurs.

with open("/tmp/.deepsource.toml", "r") as f:  
    f.write("config file.")

Bug risks

Bug risks are issues in code that can cause errors in code and breakages in production. A bug is a flaw in the code that produces undesired or incorrect results.

Code often has bug risks due to poor coding practices, lack of version control, miscommunication of requirements, unrealistic time schedules for development and buggy third-party tools.

Examples of bug risks:

Passing mutable lists or dictionaries as default arguments to a function can have unforeseen consequences. Usually, when you use a list or dictionary as the default argument to a function, you want the program to create a new list or dictionary every time that function is called. However, this is not what Python does. The first time the function is called, Python creates a persistent object for the list or dictionary. Every subsequent time the function is called, Python uses that same persistent object created during the first call to the function.

In the code below, the append function is used under the assumption that a new list with the object passed as the first argument would be returned each time that the function is called without the second argument. In reality, this is not what happens. The first time the function is called, Python creates a persistent list. Every subsequent call to append appends the value to that persistent list instead of creating a new one.

def append(number, number_list=\[]):  
    number_list.append(number)  
    print(number_list)  
    return number_list

append(5) # expecting: [5], actual: [5]  
append(7) # expecting: [7], actual: [5, 7]  
append(2) # expecting: [2], actual: [5, 7, 2]

It is a good practice to use a sentinel value to denote an empty list, set or dictionary. This means if you want the function to return a singleton list each time this function is called without the second argument, you should use a sentinel value to represent this use case and then modify the function's body to support this scenario.

# the keyword None is the sentinel value representing empty list
def append(number, number_list=None):  
    if number_list is None:  
        number_list = \[]  
    number_list.append(number)  
    print(number_list)  
    return number_list
  
append(5) # expecting: [5], actual: [5]  
append(7) # expecting: [7], actual: [7]  
append(2) # expecting: [2], actual: [2]

Performance issues

Performance issues are issues that impact the performance of code being executed by slowing it down. Considerable performance gains can be obtained when the appropriate functions and directives are used.

Examples of performance issues:

Checking for membership of a key in a list can potentially take n iterations to complete, where n is the number of items in the list. If possible, change the list to a set or dictionary instead because Python can search for items in a set or dictionary by directly accessing them without iterations, which is much more efficient.

The code below defines a listland then uses the expression if 3 in l: to check if the number 3 exists in the list. This is inefficient. Behind the scenes, Python iterates through the list until it finds the number or reaches the end of the list.

l = [1, 2, 3, 4]
# iterates over three elements in the list
if 3 in l:  
    print("The number 3 is in the list.")  
else:  
    print("The number 3 is NOT in the list.")

In the modified code below, the list has been changed to a set. This is much more efficient behind the scenes, as Python can attempt to directly access the target number in the set, rather than iterate through every item in the list and compare every item to the target number.

s = set([1, 2, 3, 4])  
if 3 in s:  
    print("The number 3 is in the list.")  
else:  
    print("The number 3 is NOT in the list.")

Security issues

A bug in code that could potentially be used to compromise security is a security vulnerability issue. Using libraries and tools that are out-of-date or have known security issues can also introduce vulnerabilities in the system.

A highly dynamic language like Python that gives you many ways to change the runtime behavior of code and even dynamically execute new code is powerful but can be a security risk as well.

The three main types of security vulnerabilities based on their more extrinsic weaknesses are:

  • Porous defenses
  • Risky resource management
  • Insecure interaction between components

Examples of security vulnerabilities:

The exec statement enables you to execute arbitrary Python code stored in literal strings dynamically. Building a complex string of Python code and then passing that code to exec results in code that is hard to read and hard to test. Anytime the "Use of exec" error is encountered, you should go back to the code and check if there is a clearer, more direct way to accomplish the task.

The sample code below composes a literal string containing Python code and then passes that string to exec for execution. This is an indirect and confusing way to program in Python.

s = "print(\"Hello, World!\")"  
exec s

In most scenarios, you can easily refactor the code to avoid the use of exec. In the example below, the use of exec has been removed and replaced by a function.

def print_hello_world():  
    print("Hello, World!")

print_hello_world()

The assert statement is a debugging aid that tests a condition. If the condition is true, it does nothing, and your program continues to execute. But if the assert condition evaluates to false, it raises an AssertionError exception with an optional error message.

In the code below, an assert statement is used in application logic, which is discouraged. Asserts can be turned off globally in the Python interpreter. Don’t rely on assert expressions to be executed for data validation or data processing.

def delete_product(product_id, user):  
    assert user.is_admin(), 'Must have admin privileges to delete'  
    assert store.product_exists(product_id), 'Unknown product id'  
    store.find_product(product_id).delete()

Assert statements should ideally be used only in tests, and to verify invariants while debugging. Instead of using assert in production code, you could do your validation with regular if-statements and raise validation exceptions if necessary.

def delete_product(product_id, user):  
    if not user.is_admin():  
        raise AuthError('Must have admin privileges to delete')

if not store.product_exists(product_id):
    raise ValueError('Unknown product id')

store.find_product(product_id).delete()

Style issues

Style issues are violations in the code format according to a style guide. If the code does not follow the specified code style guidelines, it fails to express its intent in the most readable way.

Code style can be boiled down to anything that is a stylistic choice in the code that has no effect on the behavior of the code

Any large code base with multiple team members should look as if only one programmer wrote it. If a team agrees on a given style it can help keep the code consistent.

Examples of style issues:

Per the PEP-8 Style Guide, all Python code should be consistently indented with 4 spaces, never tabs.

The following code mixes spaces and tabs for indentation. The print("Hello, World!") statement is indented with a tab. The print("Goodbye, World!") statement is indented with 4 spaces.

def print_hello_world():  
    # indented with tab  
    print("Hello, World!")

def print_goodbye_world():  
    # indented with 4 spaces  
    print("Goodbye, World!")

All Python code should be consistently indented with 4 spaces as in the code below.

def print_hello_world():  
    print("Hello, World!") # indented with 4 spaces

def print_goodbye_world():  
    print("Goodbye, World!") # indented with 4 spaces

In Python, there should be a whitespace after the characters,, ; , and :. For instance, in the code below, there is a missing whitespace after ,.

class BaseNumberGenerator:  
   def **init**(self):  
      self.limits = (1,10)

   def get_number(self, min_max):  
      raise NotImplemented

Documentation issues

Documentation issues are caused if certain parts of the code are left undocumented. Documentation is a collection of easy-to-understand images and written descriptions that describe what a codebase does and how it can be used.

Documentation is important because it ensures that the next time you dive into a codebase, you won't have to take as much time to get up to speed.

Examples of documentation issues:

PEP-8 mandates that all public modules, classes, functions, and methods should have a documentation string. A documentation string is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a string becomes the doc special attribute of that object.

Ensuring that every public module, class, function, and method is documented makes it easier for other developers to maintain the code.

If a module, class, function, or method needs to be public, add a documentation string that describes the purpose or use of the object (see PEP-257 for guidelines). If the object does not need to be public then make it "private" by changing its name from xxx to _xxx.

The following simple, public function should be updated to include a documentation string immediately after the def line.

def add(x, y):  
    return x + y

You might insert the documentation string: """Return the sum of x and y.""" on line 2 as shown in the code below.

def add(x, y):  
    """Return the sum of x and y."""  
    return x + y