java-topology/defects/php/unit/PhpNamedArgTest.java
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

153 lines
5.2 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.HashMap;
import java.util.Map;
/**
* CWE-407 unit test: php-0001 + php-0002
*
* Models PHP's zend_get_arg_num() / zend_get_arg_offset_by_name():
* resolving named argument → positional index.
*
* DEFECT (zend_compile.c:3757, zend_execute.c:5479):
* For each of N named args, scan M function params linearly.
* Total: O(N × M). Upstream code has explicit "TODO: Use a hash table?"
*
* FIX: build a HashMap<name, index> once per function signature; each lookup is O(1).
* Total: O(M + N).
*
* Asserts: slowOps > fastOps * 10 at N=M=50 (actual ratio ≈ 50×).
*/
public class PhpNamedArgTest {
/**
* Simulate resolving N named arguments against M function parameters
* using linear scan (defective path).
*
* @param M number of function parameters
* @param N number of named arguments being passed (same set as params)
* @return total string-comparison operations performed
*/
static long slow(int M, int N) {
// Build param list (function signature)
String[] params = new String[M];
for (int i = 0; i < M; i++) {
params[i] = "param" + i;
}
// Resolve N named args in reverse order (worst case for linear scan)
long ops = 0;
for (int j = N - 1; j >= 0; j--) {
String argName = "param" + j;
// Linear scan — mirrors zend_get_arg_num loop
for (int i = 0; i < M; i++) {
ops++;
if (params[i].equals(argName)) {
break;
}
}
}
return ops;
}
/**
* Simulate the patched path: build HashMap once, then O(1) lookup per arg.
*
* @param M number of function parameters
* @param N number of named arguments being passed
* @return total operations (M to build + N to lookup)
*/
static long fast(int M, int N) {
// Build param list
String[] params = new String[M];
for (int i = 0; i < M; i++) {
params[i] = "param" + i;
}
// Build HashMap once — O(M)
Map<String, Integer> nameMap = new HashMap<>(M * 2);
long ops = 0;
for (int i = 0; i < M; i++) {
nameMap.put(params[i], i + 1);
ops++;
}
// Resolve N named args — O(N) hash lookups
for (int j = N - 1; j >= 0; j--) {
String argName = "param" + j;
ops++; // one hash probe
nameMap.get(argName); // never null in this test
}
return ops;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: M=N=10 — slow must be >2× more expensive
// (worst-case sOps = 1+2+...+10 = 55; fOps = 10+10 = 20; ratio ≈ 2.8×)
{
total++;
long sOps = slow(10, 10);
long fOps = fast(10, 10);
boolean ok = sOps > fOps * 2L;
System.out.printf("Test 1 [M=N=10 slow=%d fast=%d ratio=%.1fx]: %s%n",
sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 2: M=N=50 — slow must be >10× more expensive
{
total++;
long sOps = slow(50, 50);
long fOps = fast(50, 50);
// Expected: sOps ≈ 50*25=1250 (avg half-scan); fOps = 50+50=100
boolean ok = sOps > fOps * 10L;
System.out.printf("Test 2 [M=N=50 slow=%d fast=%d ratio=%.1fx]: %s%n",
sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 3: M=N=100 — slow must be >25× more expensive
{
total++;
long sOps = slow(100, 100);
long fOps = fast(100, 100);
// Expected: sOps ≈ 5050 (worst-case reverse); fOps = 200
boolean ok = sOps > fOps * 25L;
System.out.printf("Test 3 [M=N=100 slow=%d fast=%d ratio=%.1fx]: %s%n",
sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 4: correctness — both return same position for each arg name
{
total++;
int M = 30;
// Defective path: resolve each param name to position
String[] params = new String[M];
for (int i = 0; i < M; i++) params[i] = "param" + i;
Map<String, Integer> fastMap = new HashMap<>();
for (int i = 0; i < M; i++) fastMap.put(params[i], i + 1);
boolean ok = true;
for (int j = 0; j < M; j++) {
// slow: linear scan result
int slowPos = -1;
for (int i = 0; i < M; i++) {
if (params[i].equals("param" + j)) { slowPos = i + 1; break; }
}
int fastPos = fastMap.getOrDefault("param" + j, -1);
if (slowPos != fastPos) { ok = false; break; }
}
System.out.printf("Test 4 [correctness M=%d match=%b]: %s%n",
M, ok, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}