Comparing instead of Assigning
Description
Comparing instead of Assigning occurs when a programmer uses the comparison operator (==) where an assignment operator (=) was intended. This is the inverse of CWE-481. While less common because comparison in a statement context often triggers warnings, this error can occur in complex expressions, return statements, or when the comparison result is mistakenly believed to perform assignment. The intended side effect (value assignment) never occurs, leaving variables in their original state.
Risk
Comparing instead of assigning causes variables to retain unintended values, leading to logic errors and potential security vulnerabilities. In initialization code, objects may remain uninitialized. In state management, transitions fail to occur. In security contexts, flags may not be set properly, leaving systems in insecure states. Error handling may fail to record error codes. Unlike assignment-instead-of-comparison, this bug often fails silently—the program continues with stale or incorrect values.
Solution
Enable compiler warnings for statements with no effect. Use static analysis tools that detect comparisons with unused results. Make assignments explicit and on separate lines when possible. Avoid complex expressions that mix assignments and comparisons. Use code review to verify state transitions and value assignments. In debugging, verify that expected variable values change. Consider IDE features that highlight unused expression results.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Incorrect State Variables retain wrong values, corrupting program state. |
| Security | Scope: Flag Not Set Security flags may remain in insecure default state. |
| Reliability | Scope: Logic Errors Program logic fails due to unperformed assignments. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: Comparison instead of assignment
void initialize_vulnerable(Config* config) {
config->enabled == 1; // BUG! Compares but doesn't assign!
config->timeout == 30; // BUG! Config values unchanged!
config->retries == 3; // BUG! Original values remain!
}
// VULNERABLE: Error flag not set
int process_data_vulnerable(char* data, int* error_flag) {
if (data == NULL) {
*error_flag == ERROR_NULL; // BUG! Doesn't set flag!
return -1;
}
if (!validate(data)) {
*error_flag == ERROR_INVALID; // BUG! Flag unchanged!
return -1;
}
return 0;
}
// VULNERABLE: State transition fails
typedef enum { INIT, RUNNING, STOPPED } State;
void start_service_vulnerable(Service* svc) {
if (svc->state == INIT) {
svc->state == RUNNING; // BUG! State doesn't change!
// Service thinks it started but state is still INIT
}
}
// VULNERABLE: Pointer not assigned
int* get_buffer_vulnerable(int size) {
int* buffer;
buffer == malloc(size * sizeof(int)); // BUG! Comparison!
// buffer is uninitialized, memory leaked!
return buffer; // Returns garbage pointer!
}
// VULNERABLE: Counter not incremented
void count_items_vulnerable(Item* items, int count, int* total) {
*total = 0;
for (int i = 0; i < count; i++) {
if (items[i].valid) {
*total == *total + 1; // BUG! Doesn't increment!
// *total stays 0
}
}
}
// VULNERABLE: Return value comparison
int calculate_vulnerable(int a, int b) {
int result;
result == a + b; // BUG! result is uninitialized/unchanged
return result; // Returns garbage!
}
// VULNERABLE: In ternary expression
void set_mode_vulnerable(int* mode, int condition) {
condition ? (*mode == MODE_A) : (*mode == MODE_B);
// BUG! Both branches compare, neither assigns!
// mode is unchanged
}
// VULNERABLE: Security flag not set
int authenticate_vulnerable(User* user, const char* password) {
if (check_password(user, password)) {
user->authenticated == 1; // BUG! Never authenticated!
user->login_time == time(NULL); // BUG! Time not recorded!
return SUCCESS;
}
return FAILURE;
}
// VULNERABLE: C++ member initialization
class VulnerableClass {
int value;
bool initialized;
public:
void init(int v) {
value == v; // BUG! value unchanged
initialized == true; // BUG! still uninitialized
}
void reset() {
value == 0; // BUG! not reset
initialized == false; // BUG! flag not cleared
}
};
// VULNERABLE: Smart pointer assignment
void create_resource_vulnerable() {
std::unique_ptr<Resource> ptr;
ptr == std::make_unique<Resource>(); // BUG! Comparison!
// ptr is still nullptr!
if (ptr) {
ptr->use(); // Never reached
}
}
// VULNERABLE: String assignment
void update_name_vulnerable(std::string& name, const std::string& newName) {
name == newName; // BUG! name unchanged!
}
// VULNERABLE: Vector element update
void update_element_vulnerable(std::vector<int>& vec, int index, int value) {
if (index < vec.size()) {
vec[index] == value; // BUG! Comparison, not assignment!
}
}
// VULNERABLE: Object state
class VulnerableState {
State current;
public:
void transition(State next) {
if (isValidTransition(current, next)) {
current == next; // BUG! State unchanged!
}
}
};
// JavaScript - comparison result ignored
// VULNERABLE: Property assignment fails
function initConfigVulnerable(config) {
config.enabled == true; // BUG! Comparison!
config.timeout == 30; // BUG! Properties unchanged!
}
// VULNERABLE: Variable assignment fails
function processVulnerable(data) {
let result;
result == processData(data); // BUG! result is undefined!
return result;
}
// VULNERABLE: Object property update
function updateUserVulnerable(user, newData) {
user.name == newData.name; // BUG! name not updated
user.email == newData.email; // BUG! email not updated
}
// VULNERABLE: Counter in loop
function countValidVulnerable(items) {
let count = 0;
for (let item of items) {
if (item.valid) {
count == count + 1; // BUG! count stays 0
}
}
return count; // Always returns 0
}
Fixed Code
// SAFE: Correct assignment
void initialize_safe(Config* config) {
config->enabled = 1; // Correct assignment
config->timeout = 30; // Values properly set
config->retries = 3;
}
// SAFE: Error flag properly set
int process_data_safe(char* data, int* error_flag) {
if (data == NULL) {
*error_flag = ERROR_NULL; // Correct assignment
return -1;
}
if (!validate(data)) {
*error_flag = ERROR_INVALID; // Flag properly set
return -1;
}
*error_flag = 0; // Clear on success
return 0;
}
// SAFE: State transition works
void start_service_safe(Service* svc) {
if (svc->state == INIT) {
svc->state = RUNNING; // Correct assignment
log_transition(INIT, RUNNING);
}
}
// SAFE: Pointer properly assigned
int* get_buffer_safe(int size) {
int* buffer = malloc(size * sizeof(int)); // Assign in declaration
if (buffer == NULL) {
return NULL;
}
return buffer;
}
// Or split for clarity
int* get_buffer_safe_v2(int size) {
int* buffer;
buffer = malloc(size * sizeof(int)); // Clear assignment
return buffer;
}
// SAFE: Counter properly incremented
void count_items_safe(Item* items, int count, int* total) {
*total = 0;
for (int i = 0; i < count; i++) {
if (items[i].valid) {
*total = *total + 1; // Correct assignment
// Or: (*total)++;
}
}
}
// SAFE: Return value properly assigned
int calculate_safe(int a, int b) {
int result = a + b; // Initialize in declaration
return result;
}
// SAFE: Ternary with assignment
void set_mode_safe(int* mode, int condition) {
*mode = condition ? MODE_A : MODE_B; // Correct assignment
}
// SAFE: Security flags properly set
int authenticate_safe(User* user, const char* password) {
if (check_password(user, password)) {
user->authenticated = 1; // Correct assignment
user->login_time = time(NULL); // Time recorded
log_successful_login(user);
return SUCCESS;
}
user->authenticated = 0; // Explicitly clear on failure
return FAILURE;
}
// SAFE: Using assert to verify assignments
void initialize_with_verify(Config* config) {
config->enabled = 1;
config->timeout = 30;
config->retries = 3;
// Verify in debug builds
assert(config->enabled == 1);
assert(config->timeout == 30);
assert(config->retries == 3);
}
// SAFE: C++ with correct assignments
class SafeClass {
int value = 0; // Initialize in declaration
bool initialized = false;
public:
void init(int v) {
value = v; // Correct assignment
initialized = true; // Flag properly set
}
void reset() {
value = 0; // Correct reset
initialized = false; // Flag cleared
}
// Use getter to verify
bool isInitialized() const {
return initialized;
}
};
// SAFE: Smart pointer assignment
void create_resource_safe() {
auto ptr = std::make_unique<Resource>(); // Direct initialization
// Or:
std::unique_ptr<Resource> ptr2;
ptr2 = std::make_unique<Resource>(); // Correct assignment
if (ptr) {
ptr->use(); // Works correctly
}
}
// SAFE: String assignment
void update_name_safe(std::string& name, const std::string& newName) {
name = newName; // Correct assignment
}
// SAFE: Vector element update
void update_element_safe(std::vector<int>& vec, int index, int value) {
if (index < vec.size()) {
vec[index] = value; // Correct assignment
}
}
// Or use at() for bounds checking
void update_element_safe_v2(std::vector<int>& vec, size_t index, int value) {
vec.at(index) = value; // Throws if out of bounds
}
// SAFE: State management
class SafeState {
State current = State::INIT;
public:
bool transition(State next) {
if (isValidTransition(current, next)) {
current = next; // Correct assignment
return true;
}
return false;
}
State getState() const {
return current;
}
};
// SAFE: Using structured bindings (C++17)
auto createAndInit() {
Config config;
config.enabled = true;
config.timeout = 30;
return config;
}
// SAFE: JavaScript with correct assignments
// SAFE: Property assignment
function initConfigSafe(config) {
config.enabled = true; // Correct assignment
config.timeout = 30;
}
// SAFE: Variable assignment
function processSafe(data) {
const result = processData(data); // Correct assignment
return result;
}
// SAFE: Object property update
function updateUserSafe(user, newData) {
user.name = newData.name; // Correct assignment
user.email = newData.email;
}
// Or use Object.assign
function updateUserSafe2(user, newData) {
Object.assign(user, {
name: newData.name,
email: newData.email
});
}
// Or spread operator
function updateUserSafe3(user, newData) {
return { ...user, ...newData };
}
// SAFE: Counter properly incremented
function countValidSafe(items) {
let count = 0;
for (const item of items) {
if (item.valid) {
count = count + 1; // Correct assignment
// Or: count++;
// Or: count += 1;
}
}
return count;
}
// Or functional approach
function countValidFunctional(items) {
return items.filter(item => item.valid).length;
}
// ESLint will catch no-unused-expressions
// "use strict" helps catch some issues
Exploited in the Wild
Initialization Failures
Software has shipped with uninitialized configurations due to comparison-instead-of-assignment bugs, causing unexpected behavior in production.
Security Flag Bypasses
Security flags that were never properly set due to this bug have left systems in insecure default states.
State Machine Bugs
Critical systems have had state transitions fail silently, causing incorrect behavior when states weren't updated.
Tools to test/exploit
-
GCC/Clang Warnings — -Wunused-value warns about comparisons with no effect.
-
ESLint — no-unused-expressions rule.
-
Coverity — detects statements with no effect.
-
clang-tidy — misc-redundant-expression.
CVE Examples
-
Various software bugs traced to initialization failures from this error pattern.
-
State management bugs in critical systems.
-
Configuration errors from failed assignments.
References
-
MITRE. "CWE-482: Comparing instead of Assigning." https://cwe.mitre.org/data/definitions/482.html
-
CERT C. "MSC12-C: Detect and remove code that has no effect or is never executed." https://wiki.sei.cmu.edu/confluence/display/c/