CONTENTS
1. Protecting Your Program from Invalid Inputs
2. Assertions
3. Error-Handling Techniques
4. Exceptions
5. Barricade Your Program to Contain the Damage
Caused by Errors
6. Debugging Aids
DEFENSIVE PROGRAMING
Defensive programming means
coding carefully to prevent bugs
before they happen and handling
problems gracefully instead of
letting the program crash.
GARBAGE IN, GARBAGE OUT
if you put wrong or bad information into a system, you will get wrong results out of it. But in
modern software development, this is not acceptable. A good program should not produce wrong
or harmful output, even if the input is garbage or wrong.
Garbage in, nothing out
Garbage in, error message out
No garbage allowed in
PROTECTING YOUR PROGRAM FROM INVALID INPUTS
There are three general ways to handle
garbage in
[Link] the values of all data from external
sources(INPUT VALIDATION)
2. Check the values of all routine input
parameters
3. Decide how to handle bad inputs
INPUT VALIDATION
Check the values of all data from external sources When getting data
from a file, a user, the network, or some other external interface,
check to be sure that the data falls within the allowable range. Make
sure that numeric values are within tolerances and that strings are
short enough to handle.
• SQL INJECTION
• ATTEMPTED BUFFER OVERFLOWS
• INTEGER OVERFLOWS
• EMAIL VALIDATION
• PASSWORD VALIDATION
SQL INJECTION
Test this SQL Injection In testing website link given
below
Altoro Mutual
NEVER BUILD SQL BY CONCATENATING
USER INPUT INTO THE SQL STRING .
"SELECT * FROM users WHERE username = '" + username + "' AND
password = '" + password + “’”
1) What the code does
It joins (concatenates) whatever the user types for username and password
directly into the SQL text.
That means the user can supply not only data but also characters that look like
SQL (quotes, --, OR, ;, etc.).
2) How an attacker abuses it (two common tricks)
A — Comment trick (--)
If attacker sets:
username = admin' --
password = anything
SELECT * FROM USERS WHERE USERNAME =
'ADMIN' --' AND PASSWORD = 'ANYTHING'
CASE2
SELECT * FROM users WHERE username = '' OR
'1'='1' AND password = '' OR '1'='1’
Because '1'='1' is always true, the WHERE can
evaluate true and return rows → data leakage.
User input changes SQL structure, not just values
PREVENTIONS
Primary fix: Never concatenate user input into SQL. Use parameterized queries( /
placeholders so SQL text is fixed and inputs are sent separately as data
.
(Example form:
SELECT * FROM users WHERE username = ? AND password = ?
with parameters provided separately.)
Extra layer: input validation/whitelisting for fields that have a strict format
(usernames, IDs). Validation helps but does not replace parameterization.
Paramitrized query:
The SQL command and user data are kept separate
So user input cannot change the SQL structure
harmful code
SELECT * FROM users WHERE username = ' " + user_input + " ‘;
Good code
SELECT * FROM users WHERE username = ?;
A placeholder
a symbol used inside parameterized queries that will later be replaced by actual user input.
(?,%s )
A whitelist means:
Allow only specific characters, values, or options that are safe.
You do not accept everything.
You accept only what you expect.
EMAIL VALIDATION
PASSWORD VALIDATION
CHECK THE VALUES OF ALL
ROUTINE INPUT PARAMETERS
Wrong Data Type
def add_numbers(a, b):
try:
return a + b
except TypeError:
return "Error: Both inputs must be numbers."
# Testing
print(add_numbers(5, 3))
print(add_numbers("5", 3))
Divide-by-Zero Empty Input
def divide(a, b): def greet(name):
try:
try:
if not name:
return a / b
raise ValueError("Name cannot be
except ZeroDivisionError: empty.")
return "Error: Cannot divide by zero." return f"Hello, {name}!"
# Testing except ValueError as ve:
print(divide(10, 2)) return f"Error: {ve}"
print(divide(10, 0))
# Testing
print(greet("Iqra"))
print(greet(""))
ASSERTIONS
An assertion is code that’s used during development—usually a routine or
macro—that allows a program to check itself as it runs. When an assertion is
true, that means everything is operating as expected. When it’s false, that means it
has detected an unexpected error in the code. For example, if the system
assumes that a customer information file will never have more than 50,000
records, the program might contain an assertion that the number of records is
less than or equal to 50,000. As long as the number of records is less than or
equal to 50,000, the assertion will be silent. If it encounters more than 50,000
records, however, it will loudly “assert” that an error is in the program.
ASSERTION (ASSERT )
THIS SHOULD NEVER BE WRONG. IF IT IS WRONG,
STOP THE PROGRAM
TRY–EXCEPT
SOMETHING MIGHT GO WRONG. IF IT DOES,
DON’T CRASH — HANDLE IT SAFELY.
EXAMPLE
numerator = 10
denominator = 0
assert denominator != 0, "denominator is
unexpectedly equal to 0."
result = numerator / denominator
print(result)
Assertions can be used to check assumptions such as:
Input or output value range — Check that a parameter is within allowed limits.
File/stream is open or closed — Confirm file status before or after running a routine.
File/stream position (start or end) — Ensure the pointer is at the expected position when a
routine begins or ends.
File mode (read/write) — Verify that the file is opened in the correct mode (read-only, write-only,
or both).
Pointer is non-null — Make sure a pointer/reference is valid before using it.
Container size capacity — Check that an array/list has enough space for expected elements.
Table or data is initialized — Confirm that a data structure is properly filled before use.
Container empty or full — Ensure a stack, queue, or list is empty or filled as expected at the
start or end.
High-speed result vs clear version result — Verify that the optimized algorithm gives the same
result as the simpler one.