# A14 — SocketPlugin: recvfrom uses file-static lastError instead of a local

Bug ref      : always.md A.14 ; pharo.md §5.17
Severity     : MEDIUM (cross-socket error-state pollution under concurrent UDP I/O)
File         : extracted/plugins/SocketPlugin/src/common/SocketPluginImpl.c
Lines (HEAD) : 271 (file-static), 2485-2512 (`sqSocketReceiveUDPDataBufCount`)

## Problem

`SocketPluginImpl.c` declares a file-static `int lastError` at line
271 that the name-resolver uses (`sqResolverError`, etc.).
Adjacent socket I/O functions shadow it with a local `int lastError`
(see `sqSocketSendUDPToSizeDataBufCount` line 2471, `sqSocketReceiveDataBuf`
line 1325, `sqSocketSendDataBufCount` line 1385, every other path).

`sqSocketReceiveUDPDataBufCount` does **not** shadow it:

```c
sqInt sqSocketReceiveUDPDataBufCount(SocketPtr s, char *buf, sqInt bufSize)
{
  ...
      int nread= recvfrom(SOCKET(s), buf, bufSize, 0, &SOCKETPEER(s).sa, &saddrSize);

      lastError = getLastSocketError();           // <-- file-static!

      if (nread >= 0) { ... }
      ...
      if (lastError == ERROR_WOULD_BLOCK)
    	  return 0;

      SOCKETERROR(s)= lastError;
```

Every UDP recv stores the resulting errno into the resolver's shared
state. A concurrent resolver call then sees a stale error code from
a UDP socket; conversely, an in-flight resolver error is clobbered
by the next UDP recv on any thread.

## Fix

Shadow with a local, matching the pattern of every other socket I/O
function in this file. This is purely a one-line change.

```diff
--- a/extracted/plugins/SocketPlugin/src/common/SocketPluginImpl.c
+++ b/extracted/plugins/SocketPlugin/src/common/SocketPluginImpl.c
@@ -2492,7 +2492,7 @@ sqInt sqSocketReceiveUDPDataBufCount(SocketPtr s, char *buf, sqInt bufSize)
 	  socklen_t saddrSize= sizeof(SOCKETPEER(s));
 
       int nread= recvfrom(SOCKET(s), buf, bufSize, 0, &SOCKETPEER(s).sa, &saddrSize);
 
-      lastError = getLastSocketError();
+      int lastError = getLastSocketError();
 
       if (nread >= 0) {
     	  SOCKETPEERSIZE(s)= saddrSize;
```

## Test plan

- Run a name-resolver lookup concurrently with a UDP receive in a
  separate thread; verify the resolver's `sqResolverError` reflects
  only its own failures.
- Static check that no other socket I/O path writes the file-static:

      $ git grep -n '\<lastError\s*=\s*getLastSocketError' \
            extracted/plugins/SocketPlugin/src/common/SocketPluginImpl.c

  All matches are `int lastError = getLastSocketError()` (local
  shadow) after this fix.

## Risk notes

- Equivalent local-shadow pattern is already used in every adjacent
  function. This change makes the UDP recv consistent with the rest
  of the file.
- The file-static `lastError` continues to be the resolver's error
  channel.
