Skip to content

Commit ff64256

Browse files
authored
Switch JS Engine from SpiderMonkey to QuickJS (#228)
- SpiderMonkey we were using is very old, probably riddled with bugs. - It's time to move on to a more modern and more appropriate engine.
1 parent e2234e7 commit ff64256

15 files changed

Lines changed: 81633 additions & 174 deletions

File tree

.github/workflows/build.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,18 @@ jobs:
230230

231231
- name: Build wheel using cibuildwheel
232232
run: |
233-
cp src/spidermonkey/libjs.a src/pacparser.o src/pacparser.h src/pymod
234-
cd src/pymod && python -m cibuildwheel --output-dir dist
233+
cd src/pymod && python setup.py clean --all
234+
cp -r ../quickjs ../pacparser.c ../pac_utils.h ../pacparser.h .
235+
python -m cibuildwheel --output-dir dist
235236
env:
236237
CIBW_BUILD: "cp{37,38,39,310,311,312}-manylinux*64"
237238
CIBW_ENVIRONMENT: "PACPARSER_VERSION=${{ env.PACPARSER_VERSION }}"
239+
CIBW_BEFORE_BUILD: >-
240+
cd {project} &&
241+
make -C quickjs clean &&
242+
make -C quickjs CFLAGS='-fPIC' &&
243+
cc -g -Wall -DVERSION=$PACPARSER_VERSION -Iquickjs -fPIC -c pacparser.c -o pacparser.o &&
244+
cp quickjs/libquickjs.a .
238245
239246
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
240247
id: src_changes

.github/workflows/sonar.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ jobs:
1515
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
1616
with:
1717
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
18-
- name: Install sonar-scanner and build-wrapper
19-
uses: SonarSource/sonarcloud-github-c-cpp@44cc4d3d487fbc35e5c29b0a9d717be218d3a0e8 # v3.2.0
20-
- name: Run sonar-scanner
21-
env:
22-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
18+
- name: Install bear
19+
run: sudo apt install -y bear
20+
- name: Generate compilation database
2421
run: |
25-
sudo apt install -y bear
2622
make -C src clean
2723
bear -- make -C src
28-
sonar-scanner
24+
- name: SonarCloud Scan
25+
uses: SonarSource/sonarqube-scan-action@v5.0.0
26+
env:
27+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ src/pactester
4040
*.json
4141
pactester
4242
info.plist
43+
44+
.gemini/tmp

CLAUDE.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Pacparser is a C library (with Python bindings) for parsing proxy auto-config (PAC) files. It embeds QuickJS JavaScript engine to evaluate PAC scripts and implements the standard PAC helper functions (e.g., `dnsDomainIs`, `isInNet`, `myIpAddress`). Licensed under LGPL.
8+
9+
## Build Commands
10+
11+
All build commands run from the repo root using `make -C src`.
12+
13+
```bash
14+
# Build C library and pactester CLI (also runs tests)
15+
make -C src
16+
17+
# Build without internet-dependent tests
18+
NO_INTERNET=1 make -C src
19+
20+
# Build Python module (also runs Python tests)
21+
make -C src pymod
22+
23+
# Install C library and pactester
24+
sudo make -C src install
25+
26+
# Install Python module
27+
sudo make -C src install-pymod
28+
29+
# Clean all build artifacts
30+
make -C src clean
31+
32+
# Windows build (requires MinGW/MSYS2)
33+
make -C src -f Makefile.win32
34+
```
35+
36+
## Testing
37+
38+
Tests run automatically as part of the build (`make -C src` runs `testpactester` target).
39+
40+
```bash
41+
# Run C tests manually
42+
../tests/runtests.sh # from src/
43+
NO_INTERNET=1 ../tests/runtests.sh # skip DNS-dependent tests
44+
45+
# Run Python tests manually
46+
python ../tests/runtests.py # from src/
47+
NO_INTERNET=1 python ../tests/runtests.py
48+
```
49+
50+
Test data is in `tests/testdata` with format: `<pactester params> | <expected result>`. Tests use `tests/proxy.pac` as the PAC file. Set `DEBUG=1` for verbose test output.
51+
52+
## Architecture
53+
54+
### Build Flow
55+
1. QuickJS engine compiles to `src/quickjs/libquickjs.a`
56+
2. `pacparser.c` compiles against QuickJS headers to `pacparser.o`
57+
3. Shared library (`libpacparser.so.1` / `.dylib` / `.dll`) links `pacparser.o` + `libquickjs.a`
58+
4. `pactester` CLI statically links against `libpacparser.a`
59+
5. Python C extension (`_pacparser`) wraps `pacparser.o` + `libquickjs.a` via setuptools
60+
61+
### Key Source Files
62+
63+
- **`src/pacparser.c`** — Core library. Initializes QuickJS context, evaluates PAC scripts, implements DNS helper functions (`dns_resolve`, `my_ip`). All public API functions live here.
64+
- **`src/pacparser.h`** — Public C API (9 functions: `init`, `parse_pac_file`, `parse_pac_string`, `find_proxy`, `just_find_proxy`, `cleanup`, `setmyip`, `set_error_printer`, `version`).
65+
- **`src/pac_utils.h`** — PAC standard JavaScript functions embedded as a C string. This is Mozilla's PAC utility implementation defining `dnsDomainIs()`, `isInNet()`, `shExpMatch()`, etc.
66+
- **`src/pactester.c`** — CLI tool wrapping the library. Usage: `pactester -p <pacfile> -u <url> [-c client_ip] [-f urlslist]`.
67+
- **`src/pymod/pacparser/__init__.py`** — Python API wrapper. Adds host auto-extraction from URLs and the `URLError` exception.
68+
- **`src/pymod/pacparser_py.c`** — Python C extension binding `_pacparser` methods to the C library.
69+
- **`src/pymod/setup.py`** — Python build config. Version derived from git tags.
70+
71+
### Platform Handling
72+
The `src/Makefile` detects OS via `uname` and adjusts shared library naming, linking flags, and compiler flags for Linux, macOS, and FreeBSD. Windows uses `src/Makefile.win32` with MinGW.

sonar-project.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ sonar.projectKey=manugarg_pacparser
22
sonar.organization=manugarg
33
sonar.projectName=pacparser
44
sonar.sources=src
5+
sonar.exclusions=src/quickjs/**/*,src/**/quickjs.c,src/**/quickjs.h
56
sonar.cfamily.compile-commands=compile_commands.json
67
sonar.sourceEncoding=UTF-8
78
sonar.host.url=https://sonarcloud.io

src/Makefile

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ LIB_VER = 1
4545
SO_SUFFIX = so
4646
LIBRARY = $(LIBRARY_NAME).$(SO_SUFFIX).$(LIB_VER)
4747
MKSHLIB = $(CC) -shared
48-
LIB_OPTS = -Wl,-soname=$(LIBRARY) -Wl,-exclude-libs=libjs.a
48+
LIB_OPTS = -Wl,-soname=$(LIBRARY) -Wl,-exclude-libs=libquickjs.a
4949
SHFLAGS = -fPIC
50-
SMCFLAGS = -DHAVE_VA_COPY -DVA_COPY=va_copy
5150

5251
ifeq ($(OS_ARCH),FreeBSD)
5352
PREFIX ?= /usr/local
@@ -62,24 +61,17 @@ ifeq ($(OS_ARCH),Darwin)
6261
MKSHLIB = $(CC) -dynamiclib -framework System
6362
LIB_OPTS = -install_name $(PREFIX)/lib/$(notdir $@)
6463
SHFLAGS =
65-
ifeq ($(MAC_GT_OS11),false)
66-
MAC_MINOR_VERSION := $(shell sw_vers -productVersion | cut -d. -f2)
67-
MAC_GT_10_5 := $(shell [ $(MAC_MINOR_VERSION) -le 5 ] && echo false)
68-
ifeq ($(MAC_GT_10_5),false)
69-
SMCFLAGS =
70-
endif
71-
endif
7264
endif
7365

7466
PREFIX ?= /usr
75-
MAINT_CFLAGS := -g -DXP_UNIX -Wall -DVERSION=$(VERSION)
67+
MAINT_CFLAGS := -g -Wall -DVERSION=$(VERSION)
7668

7769
ifndef PYTHON
7870
PYTHON = python
7971
endif
8072

81-
# Spidermonkey library.
82-
MAINT_CFLAGS += -Ispidermonkey/js/src
73+
# QuickJS library.
74+
MAINT_CFLAGS += -Iquickjs
8375

8476
LIBRARY_LINK = $(LIBRARY_NAME).$(SO_SUFFIX)
8577
PREFIX := $(DESTDIR)$(PREFIX)
@@ -92,28 +84,24 @@ MAN_PREFIX = $(PREFIX)/share/man
9284
.PHONY: clean pymod install-pymod
9385
all: testpactester
9486

95-
jsapi_buildstamp: spidermonkey/js/src
96-
cd spidermonkey && SMCFLAGS="$(SHFLAGS) $(SMCFLAGS)" $(MAKE) jsapi
97-
touch jsapi_buildstamp
87+
quickjs/libquickjs.a:
88+
cd quickjs && $(MAKE) CFLAGS="$(SHFLAGS)"
9889

99-
spidermonkey/libjs.a: spidermonkey/js/src
100-
cd spidermonkey && SMCFLAGS="$(SHFLAGS) $(SMCFLAGS)" $(MAKE) jslib
101-
102-
pacparser.o: pacparser.c pac_utils.h pacparser.h jsapi_buildstamp
90+
pacparser.o: pacparser.c pac_utils.h pacparser.h
10391
$(CC) $(MAINT_CFLAGS) $(CFLAGS) $(SHFLAGS) -c pacparser.c -o pacparser.o
10492
touch pymod/pacparser_o_buildstamp
10593

106-
$(LIBRARY): pacparser.o spidermonkey/libjs.a
107-
$(MKSHLIB) $(MAINT_CFLAGS) $(CFLAGS) $(LDFLAGS) $(LIB_OPTS) -o $(LIBRARY) pacparser.o spidermonkey/libjs.a -lm
94+
$(LIBRARY): pacparser.o quickjs/libquickjs.a
95+
$(MKSHLIB) $(MAINT_CFLAGS) $(CFLAGS) $(LDFLAGS) $(LIB_OPTS) -o $(LIBRARY) pacparser.o quickjs/libquickjs.a -lm
10896

109-
libpacparser.a: pacparser.o spidermonkey/libjs.a
110-
cp spidermonkey/libjs.a libpacparser.a
97+
libpacparser.a: pacparser.o quickjs/libquickjs.a
98+
cp quickjs/libquickjs.a libpacparser.a
11199
ar rcs libpacparser.a pacparser.o
112100

113101
$(LIBRARY_LINK): $(LIBRARY)
114102
ln -sf $(LIBRARY) $(LIBRARY_LINK)
115103

116-
pactester: pactester.c pacparser.h libpacparser.a
104+
pactester: pactester.c pacparser.h libpacparser.a
117105
$(CC) $(MAINT_CFLAGS) $(CFLAGS) $(LDFLAGS) pactester.c libpacparser.a -o pactester -lm -L. -I.
118106

119107
testpactester: pactester $(LIBRARY_LINK)
@@ -149,11 +137,11 @@ dist: all
149137
zip -r $${bindir}.zip $${bindir}/.
150138

151139
# Targets to build python module
152-
pymod: pacparser.o pacparser.h spidermonkey/libjs.a
140+
pymod: pacparser.o pacparser.h quickjs/libquickjs.a
153141
cd pymod && ARCHFLAGS="" $(PYTHON) setup.py build
154142
$(PYTHON) ../tests/runtests.py
155143

156-
pymod-dist: pacparser.o pacparser.h spidermonkey/libjs.a
144+
pymod-dist: pacparser.o pacparser.h quickjs/libquickjs.a
157145
cd pymod && ARCHFLAGS="" $(PYTHON) setup.py build
158146
cd pymod && ARCHFLAGS="" $(PYTHON) setup.py dist
159147
$(PYTHON) ../tests/runtests.py
@@ -162,7 +150,7 @@ install-pymod: pymod
162150
cd pymod && ARCHFLAGS="" $(PYTHON) setup.py install --root="$(DESTDIR)/" $(EXTRA_ARGS)
163151

164152
clean:
165-
rm -f $(LIBRARY_LINK) $(LIBRARY) pacparser.o pactester pymod/pacparser_o_buildstamp jsapi_buildstamp
153+
rm -f $(LIBRARY_LINK) $(LIBRARY) pacparser.o pactester pymod/pacparser_o_buildstamp libpacparser.a
166154
rm -rf dist
167155
cd pymod && $(PYTHON) setup.py clean --all
168-
cd spidermonkey && $(MAKE) clean
156+
cd quickjs && $(MAKE) clean

src/Makefile.win32

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
# packaging script.
2323
-include version.mk
2424

25-
ifeq ($(OS),Windows_NT)
25+
ifeq ($(OS),Windows_NT)
2626
RM = del /Q /F
2727
CP = copy /Y
2828
ifdef ComSpec
@@ -39,32 +39,32 @@ endif
3939
VERSION ?= $(shell git describe --always --tags --candidate=100)
4040

4141
LIB_VER=1
42-
CFLAGS=-g -DXP_WIN -DWINVER=0x0501 -DVERSION=$(VERSION) -Ispidermonkey/js/src -Wall
42+
CFLAGS=-g -DWINVER=0x0501 -DVERSION=$(VERSION) -Iquickjs -Wall
4343
CC=gcc
4444
PYTHON ?= python
4545

4646
.PHONY: clean pymod install-pymod
4747

4848
all: pacparser.dll pactester
4949

50-
pacparser.o: pacparser.c pac_utils.h js.lib
50+
pacparser.o: pacparser.c pac_utils.h quickjs/libquickjs.a
5151
$(CC) $(CFLAGS) -c pacparser.c -o pacparser.o
5252

53-
js.lib:
54-
$(MAKE) -C spidermonkey -f Makefile.win32
53+
quickjs/libquickjs.a:
54+
$(MAKE) -C quickjs -f Makefile.win32
5555

56-
pacparser.dll: pacparser.o spidermonkey/js.lib
56+
pacparser.dll: pacparser.o quickjs/libquickjs.a
5757
$(CC) -shared -o pacparser.dll \
5858
-Wl,--output-def,pacparser.def \
5959
-Wl,--out-implib,libpacparser.a \
6060
-Wl,--export-all-symbols \
61-
pacparser.o -ljs -Lspidermonkey -lws2_32
61+
pacparser.o quickjs/libquickjs.a -lws2_32
6262

6363
pacparser.lib: pacparser.dll pacparser.def
6464
lib /machine:i386 /def:pacparser.def
6565

66-
pactester: pactester.c pacparser.h pacparser.o
67-
$(CC) pactester.c pacparser.o -o pactester -ljs -Lspidermonkey -lws2_32
66+
pactester: pactester.c pacparser.h pacparser.o quickjs/libquickjs.a
67+
$(CC) pactester.c pacparser.o quickjs/libquickjs.a -o pactester -lws2_32
6868

6969
dist: pacparser.dll pactester pacparser.def
7070
if exist dist rmdir /s /q dist
@@ -82,15 +82,15 @@ dist: pacparser.dll pactester pacparser.def
8282
if exist ..\docs\html xcopy ..\docs\html dist\docs
8383

8484
# Targets to build python module
85-
pymod: pacparser.h pacparser.dll pacparser.o js.lib
85+
pymod: pacparser.h pacparser.dll pacparser.o quickjs/libquickjs.a
8686
cd pymod && $(PYTHON) setup.py build --compiler=mingw32
8787
cd .. && $(PYTHON) tests/runtests.py
8888

89-
pymod-dist: pacparser.h pacparser.dll pacparser.o js.lib
89+
pymod-dist: pacparser.h pacparser.dll pacparser.o quickjs/libquickjs.a
9090
cd pymod && $(PYTHON) setup.py build --compiler=mingw32 && $(PYTHON) setup.py dist
9191
cd .. && $(PYTHON) tests/runtests.py
9292

93-
pymod-%: pacparser.h pacparser.dll pacparser.o js.lib
93+
pymod-%: pacparser.h pacparser.dll pacparser.o quickjs/libquickjs.a
9494
cd pymod && py -$* setup.py build --compiler=mingw32
9595
cd .. && py -$* tests\runtests.py
9696

@@ -99,6 +99,6 @@ pymod-dist-%:
9999

100100
clean:
101101
$(RM) pacparser.dll *.lib pacparser.def pacparser.exp pacparser.o pactester.exe libpacparser.a
102-
$(MAKE) -C spidermonkey -f Makefile.win32 clean
102+
$(MAKE) -C quickjs -f Makefile.win32 clean
103103
cd pymod && $(PYTHON) setup.py clean --all
104104
$(RM) dist

src/pac_utils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ static const char *pacUtils =
4646
"}\n"
4747

4848
"function isInNet(ipaddr, pattern, maskstr) {\n"
49-
" var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/(ipaddr);\n"
49+
" var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n"
5050
" if (test == null) {\n"
5151
" ipaddr = dnsResolve(ipaddr);\n"
5252
" if (ipaddr == null)\n"

0 commit comments

Comments
 (0)