# UNDF: UNDF-2026-000000999 --- a/src/map.c +++ b/src/map.c @@ -2840,6 +2840,8 @@ /** * @brief Node structure for A* pathfinding. */ +/* CWE-407: A_in() and A_lowest() scan linked lists in O(N), making + the Dijkstra loop O(V^2 + E*V). Replace with array-indexed visited. */ typedef struct SysNode_ { struct SysNode_ *next; /**< Next node */ struct SysNode_ *gnext; /**< Next node in the garbage collector. */ @@ -2853,45 +2855,57 @@ /* prototypes */ static SysNode *A_newNode( StarSystem *sys ); static int A_g( const SysNode *n ); -static double A_d( const SysNode *n ); -static int A_less( const SysNode *op1, const SysNode *op2 ); -static SysNode *A_add( SysNode *first, SysNode *cur ); -static SysNode *A_rm( SysNode *first, const StarSystem *cur ); -static SysNode *A_in( SysNode *first, const StarSystem *cur ); -static SysNode *A_lowest( SysNode *first ); static void A_freeList( SysNode *first ); static int map_decorator_parse( MapDecorator *temp, const char *file ); + +/* Visited/cost tracking arrays indexed by system id. */ +static SysNode **A_open_idx = NULL; /**< open set: system id -> node or NULL */ +static SysNode **A_close_idx = NULL; /**< closed set: system id -> node or NULL */ +static int A_idx_sz = 0; /**< size of index arrays */ + +static void A_idx_ensure( int n ) +{ + if ( n <= A_idx_sz ) + return; + A_open_idx = realloc( A_open_idx, sizeof( SysNode * ) * n ); + A_close_idx = realloc( A_close_idx, sizeof( SysNode * ) * n ); + A_idx_sz = n; +} + +static void A_idx_clear( int n ) +{ + A_idx_ensure( n ); + memset( A_open_idx, 0, sizeof( SysNode * ) * n ); + memset( A_close_idx, 0, sizeof( SysNode * ) * n ); +} + /** @brief Creates a new node link to star system. */ static SysNode *A_newNode( StarSystem *sys ) { @@ -2909,65 +2923,48 @@ { return n->g; } -/** @brief Gets the d from a node. */ -static double A_d( const SysNode *n ) -{ - return n->d; -} -/** @brief op1 is less than op2. */ -static int A_less( const SysNode *op1, const SysNode *op2 ) -{ - return ( A_g( op1 ) < A_g( op2 ) ) || - ( A_g( op1 ) == A_g( op2 ) && A_d( op1 ) < A_d( op2 ) ); -} -/** @brief Adds a node to the linked list. */ -static SysNode *A_add( SysNode *first, SysNode *cur ) +/** @brief Inserts node into open list sorted by (g, d). O(N) insert but + * avoids O(N) extract-min and O(N) membership tests. */ +static SysNode *A_addSorted( SysNode *first, SysNode *cur ) { - SysNode *n; - - if ( first == NULL ) + A_open_idx[cur->sys->id] = cur; + if ( first == NULL ) { + cur->next = NULL; return cur; - - n = first; - while ( n->next != NULL ) - n = n->next; - n->next = cur; - - return first; -} -/* @brief Removes a node from a linked list. */ -static SysNode *A_rm( SysNode *first, const StarSystem *cur ) -{ - SysNode *n, *p; - - if ( first->sys == cur ) { - n = first->next; - first->next = NULL; - return n; } - - p = first; - n = p->next; - do { - if ( n->sys == cur ) { - p->next = n->next; - n->next = NULL; - break; - } - p = n; - } while ( ( n = n->next ) != NULL ); - - return first; + /* Insert before first element that is worse. */ + if ( cur->g < first->g || + ( cur->g == first->g && cur->d < first->d ) ) { + cur->next = first; + return cur; + } + SysNode *p = first; + while ( p->next != NULL && + ( p->next->g < cur->g || + ( p->next->g == cur->g && p->next->d <= cur->d ) ) ) + p = p->next; + cur->next = p->next; + p->next = cur; + return first; } -/** @brief Checks to see if node is in linked list. */ -static SysNode *A_in( SysNode *first, const StarSystem *cur ) +/** @brief Removes a node from open list. O(N) worst case but amortized + * with sorted-insert we no longer need a separate A_lowest scan. */ +static SysNode *A_rmOpen( SysNode *first, const StarSystem *cur ) { - SysNode *n; - - if ( first == NULL ) - return NULL; - - n = first; - do { - if ( n->sys == cur ) - return n; - } while ( ( n = n->next ) != NULL ); - return NULL; -} -/** @brief Returns the lowest ranking node from a linked list of nodes. */ -static SysNode *A_lowest( SysNode *first ) -{ - SysNode *lowest, *n; - - if ( first == NULL ) + A_open_idx[cur->id] = NULL; + if ( first == NULL || first->sys == cur ) { + SysNode *n = first ? first->next : NULL; + if ( first ) + first->next = NULL; return NULL; - - n = first; - lowest = n; - do { - if ( A_less( n, lowest ) ) - lowest = n; - } while ( ( n = n->next ) != NULL ); - return lowest; + } + SysNode *p = first; + while ( p->next != NULL ) { + if ( p->next->sys == cur ) { + SysNode *rem = p->next; + p->next = rem->next; + rem->next = NULL; + return first; + } + p = p->next; + } + return first; } /** @brief Frees a linked list. */ static void A_freeList( SysNode *first ) @@ -3051,14 +3048,18 @@ const vec2 *p_pos_entry = ( ojumps > 0 ) ? NULL : posstart; if ( ojumps > 0 ) { + /* Initialize index arrays for O(1) membership tests. */ + int nsys = array_size( systems_stack ); + A_idx_clear( nsys ); + /* start the linked lists */ open = closed = NULL; cur = A_newNode( ssys ); cur->parent = NULL; cur->g = 0; cur->d = 0.0; cur->pos = p_pos_entry; - open = A_add( open, cur ); /* Initial open node is the start system */ + open = A_addSorted( open, cur ); /* Initial open node is the start system */ j = 0; - while ( ( cur = A_lowest( open ) ) ) { + while ( open != NULL ) { + cur = open; /* Head of sorted list is always the lowest cost. */ int cost; /* End condition. */ if ( cur->sys == esys ) @@ -3074,7 +3075,9 @@ /* Get best from open and toss to closed */ - open = A_rm( open, cur->sys ); - closed = A_add( closed, cur ); + open = open->next; + A_open_idx[cur->sys->id] = NULL; + cur->next = closed; + closed = cur; + A_close_idx[cur->sys->id] = cur; cost = A_g( cur ) + 1; /* Base unit is jump and always increases by 1. */ for ( int i = 0; i < array_size( cur->sys->jumps ); i++ ) { @@ -3096,20 +3099,24 @@ if ( !show_hidden && jp_isFlag( jp, JP_HIDDEN ) ) continue; /* Update cost */ - const SysNode n_cost = { .g = cost, - .d = A_d( cur ) + - ( ( cur->pos != NULL ) - ? vec2_dist( cur->pos, &jp->pos ) - : 0.0 ) }; + int n_g = cost; + double n_d = cur->d + ( ( cur->pos != NULL ) + ? vec2_dist( cur->pos, &jp->pos ) + : 0.0 ); /* Check to see if it's already in the closed set. */ - ccost = A_in( closed, sys ); - if ( ( ccost != NULL ) && !A_less( &n_cost, ccost ) ) + ccost = A_close_idx[sys->id]; /* O(1) lookup */ + if ( ccost != NULL && + !( n_g < ccost->g || + ( n_g == ccost->g && n_d < ccost->d ) ) ) continue; /* Remove if it exists and current is better. */ - ocost = A_in( open, sys ); + ocost = A_open_idx[sys->id]; /* O(1) lookup */ if ( ocost != NULL ) { - if ( A_less( &n_cost, ocost ) ) - open = A_rm( open, sys ); /* New path is better */ + if ( n_g < ocost->g || + ( n_g == ocost->g && n_d < ocost->d ) ) + open = A_rmOpen( open, sys ); /* New path is better */ else continue; /* This node is worse, so ignore it. */ } @@ -3118,9 +3125,9 @@ const JumpPoint *jp_entry = jump_getTarget( cur->sys, sys ); neighbour = A_newNode( sys ); neighbour->parent = cur; - neighbour->g = n_cost.g; - neighbour->d = n_cost.d; + neighbour->g = n_g; + neighbour->d = n_d; neighbour->pos = ( jp_entry != NULL ) ? &jp_entry->pos : NULL; - open = A_add( open, neighbour ); + open = A_addSorted( open, neighbour ); } /* Safety check in case not linked. */