fqqdk/planarity (0.1.0)

Published 2026-07-04 14:25:17 +00:00 by fqqdk in fqqdk/php-planarity

Installation

{
	"repositories": [{
			"type": "composer",
			"url": ""
		}
	]
}
composer require fqqdk/planarity:0.1.0

About this package

Boyer–Myrvold planarity testing for PHP as a C extension: planarity($n, $edges) returns whether a graph is planar and, when not, the Kuratowski (K5 / K3,3) witness.

planarity

A minimal PHP extension exposing one function, planarity(), backed by the vendored Boyer–Myrvold reference (eapsuite/, BSD — see eapsuite/VENDOR.md). It answers: is a graph planar, and — when it is not — what is the Kuratowski witness that obstructs it.

planarity(int $n, array $edges): array
//   $n     — vertex count; vertices are 0 .. n-1
//   $edges — list of [u, v] integer pairs (0-based, undirected). PREREQUISITE: a SIMPLE graph —
//            unique undirected pairs, no parallel/duplicate edges. Self-loops are the one
//            exception: they are dropped for you. Duplicate edges are the caller's responsibility;
//            passing them is out of contract and may raise (Boyer's embedder rejects multigraphs).
//   →  ['planar' => bool, 'witness' => list<[u,v]>]
//        witness = the isolated Kuratowski subgraph (K5 / K3,3 subdivision) when non-planar — the
//        exact obstructing vertices, deduped & 0-based; [] when planar.
//   throws ValueError on malformed input (endpoint out of range, n out of range)

Why a vendored C extension, and how it's kept safe

We reuse Boyer's proven algorithm rather than hand-roll planarity (which is error-prone, especially the Kuratowski extraction). The safety bargain for C we did not author:

  • Our surface is tiny. One function, bounded input (PLANARITY_MAX_N), a single cleanup path (goto cleanup → one gp_Free) so no allocation can leak. planarity.c is the only authored C; the vendored library is compiled unmodified.
  • Proven with sanitizers, not by eye. ASan (overflow / use-after-free / invalid-free), UBSan (undefined behaviour), and LSan (leaks) run over the exercised paths and must be clean.

Build (host)

phpize && ./configure --enable-planarity && make

Two PHP installs may coexist on a dev host. The extension loads in the distro php (/usr/bin/php8.5); a static /usr/local/bin/php build can have dynamic loading disabled. Use the binary that loads it:

/usr/bin/php8.5 -d extension="$(pwd)/modules/planarity.so" --ri planarity

Tests

The behavioural suite is .phpt (PHP's native extension test format) under tests/, run by the run-tests.php harness that phpize drops in — no phpunit/composer needed. It covers the planar and non-planar verdicts (K5, K3,3, K6, Petersen, subdivisions — witnesses checked structurally as Kuratowski subdivisions, not by brittle edge-for-edge comparison), the trivial/boundary cases, input coercion, and every ValueError/TypeError path.

make test                      # after phpize && ./configure --enable-planarity && make

On a host without a build toolchain, the bundled image builds the extension and runs the suite:

docker build -t php-planarity .
docker run --rm php-planarity                 # runs `make test`

Note the contract: edges must form a simple graph (unique undirected pairs). Self-loops are dropped for you; passing parallel/duplicate edges is out of contract and may raise.

Safety proofs

Leaks / UB — the pure-C harness (tests/leakcheck.c), isolating the call sequence from PHP's allocator so LSan runs with no host noise:

SRCS=$(find eapsuite/graphLib -name '*.c' | tr '\n' ' ')
INCS="-Ieapsuite/graphLib -Ieapsuite/graphLib/extensionSystem -Ieapsuite/graphLib/homeomorphSearch \
      -Ieapsuite/graphLib/io -Ieapsuite/graphLib/lowLevelUtils -Ieapsuite/graphLib/planarityRelated"
gcc -fsanitize=address,undefined -g -O1 $INCS tests/leakcheck.c $SRCS -o /tmp/leakcheck
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 UBSAN_OPTIONS=halt_on_error=1 /tmp/leakcheck

ASan over the real extension (catches UB/overflow in the PHP-facing path; leaks off because a non-ASan php is noisy):

make clean
make CFLAGS="-fsanitize=address -fno-omit-frame-pointer -g -O1" EXTRA_LDFLAGS="-fsanitize=address"
LD_PRELOAD="$(gcc -print-file-name=libasan.so)" ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 \
  /usr/bin/php8.5 -d extension="$(pwd)/modules/planarity.so" <smoke script>

Valgrind over the extension. Disable Zend's pooled allocator — it frees everything at request-end and would mask a real leak. Run it over the isolated exercise (tests/valgrind-check.php), NOT phpunit: phpunit leaves its own framework objects unfreed at shutdown, which Valgrind reports as "definitely lost" (an all-PHP-VM stack with no extension frames — noise).

USE_ZEND_ALLOC=0 valgrind --leak-check=full --show-leak-kinds=definite,indirect \
  --error-exitcode=99 php tests/valgrind-check.php

Proven: 0 errors / 0 bytes lost across planar, witness, and both error paths.

Dependencies

Dependencies

ID Version
php >=8.1

Keywords

planarity graph boyer-myrvold kuratowski php-extension
Details
Composer
2026-07-04 14:25:17 +00:00
3
fqqdk
BSD-3-Clause
238 KiB
Assets (1)
Versions (1) View all
0.1.0 2026-07-04