functions that quietly return nothing
Missing & Undefined Returns
Some path through the function never hits a return, or returns null / undefined / None, and the caller treats that absence as data. Strongly typed languages drag this from a downstream mystery to a compile-time error.
A code path falls off the end, and 'nothing' silently becomes a value.
01in the wild
In the wild
The Same Bug, Solved by Types
Strongly typed languages move the unstructured-state failure from runtime to compile time, where it's cheap.
example.ts
// strictNullChecks turns a runtime crash into a red squiggle
interface User { name: string; age?: number; }
function birthday(u: User): number {
return u.age + 1; // compile error: 'u.age' is possibly undefined
}
// RIGHT: the compiler forces you to handle absence
function birthday(u: User): number {
return (u.age ?? 0) + 1;
}The optional field can't be used until you account for undefined. The bug never reaches production.
// observed
without guard: TS2532 at compile time with ??: compiles, handles missing age
example.go
// Errors are values you must thread through, not exceptions
func current(v, r float64) (float64, error) {
if r == 0 {
return 0, fmt.Errorf("resistance is zero")
}
return v / r, nil
}
i, err := current(5, 0)
if err != nil { return err } // the compiler nags if you ignore itReturning (value, error) makes the failure path part of the signature. Linters flag an ignored err.
// observed
current(5, 0) -> 0, error('resistance is zero')
err is checked before i is usedexample.kt
// Nullability is in the type system: String vs String?
fun greet(name: String) = "Hi, $name"
val maybe: String? = lookup()
greet(maybe) // compile error: String? where String expected
// RIGHT: the safe call / elvis operator handle the null
greet(maybe ?: "stranger")String and String? are different types. You cannot pass a nullable where a non-null is required without handling it.
// observed
greet(maybe): compile error greet(maybe ?: ...): 'Hi, stranger'
example.rs
// Absence is Option<T>; you cannot forget to handle None.
fn first_word(s: &str) -> Option<&str> {
s.split_whitespace().next()
}
match first_word("") {
Some(w) => println!("{w}"),
None => println!("(empty input)"), // exhaustive
}Option<T> makes 'nothing' a value the type system tracks. The match must cover None or it won't compile.
// observed
first_word("") -> None
match is exhaustive: '(empty input)'The Function That Forgets to Return
A branch with no return doesn't error; it hands back undefined / None and the caller runs with it.
example.js
// BUG: the else path has no return -> undefined
function discount(user) {
if (user.isMember) return 0.1;
// forgot the non-member case
}
const price = base * (1 - discount(guest)); // NaN: 1 - undefined
// RIGHT: every path returns; lint with consistent-return
function discount(user) {
return user.isMember ? 0.1 : 0;
}A missing return is undefined, and arithmetic on undefined is NaN: a wrong number with no stack trace.
// observed
guest: 1 - undefined -> NaN fixed: 1 - 0 -> 1
example.py
# BUG: the loop may finish without returning -> implicit None
def find_admin(users):
for u in users:
if u.role == "admin":
return u
# no admin? falls off the end -> None
admin = find_admin(users)
admin.notify() # AttributeError: 'NoneType' has no 'notify'
# RIGHT: make the 'not found' case explicit
def find_admin(users):
return next((u for u in users if u.role == "admin"), None)Python returns None when execution falls off the end. The caller crashes later, far from the function that 'forgot'.
// observed
no admin: None -> AttributeError downstream explicit: caller checks for None first
02cross-pollination
Where this compounds
Runtime Errors
- Null / Undefined Dereference × Improper Initialization
Data Corruption
- Unchecked Sentinel Corrupts a Shared Record × File & Network Access
03weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.