Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions .github/workflows/turbo-ext.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions

name: "Turbo Extension"

permissions:
contents: read

on:
pull_request:
push:
branches:
- "2.2.x"

concurrency:
group: turbo-ext-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches
cancel-in-progress: true

env:
# Pin PHP-CPP to the commit the extension was developed against.
PHP_CPP_COMMIT: "382a5f0386c8c46650e670366418e6ef55e71830"
# The php-parser version whose grammar tables and semantic actions the
# native parser engine (turbo-ext/src/parser/) was ported against.
SUPPORTED_PHP_PARSER_VERSION: "v5.8.0"

jobs:
version:
name: "Extension Version Check"
runs-on: "ubuntu-latest"
timeout-minutes: 5

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: "Checkout"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0 # git log over turbo-ext/src needs full history

- name: "Install PHP"
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2

Check notice

Code scanning / poutine

Github Action from Unverified Creator used Note

Usage of the following GitHub Actions repositories was detected in workflows
or composite actions, but their owner is not a verified creator.
with:
coverage: "none"
php-version: "8.5"

- name: "Check the shadowed PHP and C++ implementations are in sync (method parity)"
run: "php turbo-ext/bin/side-by-side.php --check"

- name: "Check the vendored php-parser matches the version the native parser engine was ported against"
run: |
INSTALLED="$(jq -r '.packages[] | select(.name == "nikic/php-parser") | .version' composer.lock)"
echo "composer.lock: $INSTALLED, native engine ported against: $SUPPORTED_PHP_PARSER_VERSION"
if [ "$INSTALLED" != "$SUPPORTED_PHP_PARSER_VERSION" ]; then
echo "::error::nikic/php-parser was updated to $INSTALLED but turbo-ext/src/parser/ was ported against $SUPPORTED_PHP_PARSER_VERSION."
echo "::error::Update procedure: diff ParserAbstract.php helpers between the two versions (grammar tables need nothing — they are read at run time), run turbo-ext/bin/generate-parser-actions.php and port any flagged closure bodies via action-overrides/, run turbo-ext/tests/parser-corpus.php until byte-identical, run the full suite, then bump SUPPORTED_PHP_PARSER_VERSION here and the extension version pin."
exit 1
fi

- name: "Check TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION against the last commit touching turbo-ext/src or a shadowed PHP class"
run: |
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
# A change to either side of a shadowed pair must bump the version:
# watch turbo-ext/src plus the repo-local PHP files from the manifest
# (vendored entries are governed by composer.lock instead).
mapfile -t MAPPED_PHP < <(jq -r '.[] | select(.vendored != true) | .php' turbo-ext/shadowed-classes.json)
EXPECTED_SHA="$(git log -1 --format=%H -- turbo-ext/src "${MAPPED_PHP[@]}" | cut -c1-7)"
ENABLER="$(sed -n "s/.*EXPECTED_EXTENSION_VERSION = '\([^']*\)'.*/\1/p" src/Turbo/TurboExtensionEnabler.php)"
echo "last commit touching turbo-ext/src or a shadowed PHP class: $EXPECTED_SHA"
echo "TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION: $ENABLER"
if [ "$ENABLER" != "$EXPECTED_SHA" ]; then
echo "::error::TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION must be the short SHA of the last commit touching turbo-ext/src/ or a PHP class listed in turbo-ext/shadowed-classes.json."
echo "::error::After changing either side, verify the implementations still match and add a follow-up commit setting the constant to that commit's short SHA (the binary bakes its version from git at build time)."
exit 1
fi

compile:
name: "Compile Extension"
runs-on: ${{ matrix.operating-system }}
timeout-minutes: 30

strategy:
fail-fast: false
matrix:
operating-system: ["ubuntu-latest", "macos-latest"]

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: "Checkout"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
persist-credentials: false
# The Makefile bakes the extension version via git log over the
# watched paths; a shallow clone would resolve every build to the
# checked-out commit itself — on pull requests the synthetic merge
# commit, which matches nothing.
fetch-depth: 0

- name: "Install PHP"
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2

Check notice

Code scanning / poutine

Github Action from Unverified Creator used Note

Usage of the following GitHub Actions repositories was detected in workflows
or composite actions, but their owner is not a verified creator.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
coverage: "none"
php-version: "8.5"

- name: "Cache PHP-CPP"
id: "cache-php-cpp"
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: "turbo-ext/PHP-CPP"
key: "php-cpp-${{ env.PHP_CPP_COMMIT }}-php-8.5-${{ matrix.operating-system }}"

- name: "Clone and build PHP-CPP"
if: steps.cache-php-cpp.outputs.cache-hit != 'true'
working-directory: "turbo-ext"
run: |
git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git PHP-CPP
git -C PHP-CPP checkout "$PHP_CPP_COMMIT"
ln -sfn include PHP-CPP/phpcpp
# On LP64 Darwin int64_t is long long, so returning long from
# count() makes the Php::Value construction ambiguous; the cast is a
# no-op on Linux, so the patch applies everywhere.
git -C PHP-CPP apply ../patches/php-cpp-base-count-int64.patch
make -C PHP-CPP -j"$(getconf _NPROCESSORS_ONLN)"

- name: "Compile phpstan_turbo with strict warnings"
working-directory: "turbo-ext"
# Exemptions, each caused by third-party macro expansions, not our code:
# -Wno-assume: zend's parameter-parsing macros expand __builtin_assume
# with (potential) side effects
# -Wno-unused-parameter: PHP_METHOD's fixed signature
# (execute_data/return_value are not used by every method)
# -Wno-unicode: zend arginfo macros stringify namespaced class names;
# clang lexes the \N in "PhpParser\NodeVisitor" as a universal
# character name (GCC ignores the unknown -Wno- flag)
run: |
ln -sfn include PHP-CPP/phpcpp
make WARN_FLAGS="-Wall -Wextra -Werror -Wno-assume -Wno-unused-parameter -Wno-unicode"

- name: "Verify the built extension reports the expected version"
run: |
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
REPORTED="$(php -d extension="$PWD/turbo-ext/phpstan_turbo.so" -r 'echo phpversion("phpstan_turbo");')"
EXPECTED="$(sed -n "s/.*EXPECTED_EXTENSION_VERSION = '\([^']*\)'.*/\1/p" src/Turbo/TurboExtensionEnabler.php)"
echo "built extension reports: $REPORTED, enabler expects: $EXPECTED"
[ "$REPORTED" = "$EXPECTED" ]

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0

Check notice

Code scanning / poutine

Github Action from Unverified Creator used Note

Usage of the following GitHub Actions repositories was detected in workflows
or composite actions, but their owner is not a verified creator.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

- name: "Smoke test (differential: native vs PHP implementations)"
run: php -d extension="$PWD/turbo-ext/phpstan_turbo.so" turbo-ext/tests/smoke.php

- name: "Signature parity (reflect native classes against the PHP twins)"
run: php -d extension="$PWD/turbo-ext/phpstan_turbo.so" turbo-ext/tests/signature-parity.php

- name: "Parser actions are generated (regenerate and diff)"
run: |
php turbo-ext/bin/generate-parser-actions.php
git diff --exit-code turbo-ext/src/parser/

- name: "Parser corpus (differential: native vs PHP ASTs must be byte-identical)"
run: php -d extension="$PWD/turbo-ext/phpstan_turbo.so" -d memory_limit=4G turbo-ext/tests/parser-corpus.php

- name: "Upload extension artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: "phpstan_turbo-${{ matrix.operating-system }}"
path: "turbo-ext/phpstan_turbo.so"
if-no-files-found: "error"

run:
name: "Run with Extension"
needs: "compile"
runs-on: ${{ matrix.operating-system }}
timeout-minutes: 60

strategy:
fail-fast: false
matrix:
operating-system: ["ubuntu-latest", "macos-latest"]
script: ["make tests", "make phpstan"]

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: "Checkout"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
persist-credentials: false

- name: "Install PHP"
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2

Check notice

Code scanning / poutine

Github Action from Unverified Creator used Note

Usage of the following GitHub Actions repositories was detected in workflows
or composite actions, but their owner is not a verified creator.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
coverage: "none"
php-version: "8.5"

- name: "Download extension artifact"
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: "phpstan_turbo-${{ matrix.operating-system }}"
path: "turbo-ext"

- name: "Install extension"
run: |
sudo bash -c "echo 'extension=$GITHUB_WORKSPACE/turbo-ext/phpstan_turbo.so' >> $(php -r 'echo php_ini_loaded_file();')"
php -m | grep phpstan_turbo

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0

Check notice

Code scanning / poutine

Github Action from Unverified Creator used Note

Usage of the following GitHub Actions repositories was detected in workflows
or composite actions, but their owner is not a verified creator.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

- name: "Verify the extension is active"
run: |
php -r '
require "src/Turbo/TurboExtensionEnabler.php";
PHPStan\Turbo\TurboExtensionEnabler::enableIfLoaded();
require "vendor/autoload.php";
if (!(new ReflectionClass("PHPStan\\Analyser\\ScopeOps"))->getParentClass() || !(new ReflectionClass("PHPStan\\Analyser\\ScopeOps"))->getParentClass()->isInternal()) {
fwrite(STDERR, "turbo extension is not shadowing ScopeOps" . PHP_EOL);
exit(1);
}
'

- name: "Run"
# The memo's per-worker footprint grows with files-per-worker; macOS
# runners have few cores, so each of their few workers analyses enough
# files to tip the default 450M limit that a many-worker Linux runner
# stays under.
env:
PHPSTAN_MEMORY_LIMIT: ${{ matrix.operating-system == 'macos-latest' && '1G' || '450M' }}
run: ${{ matrix.script }}

Check warning

Code scanning / zizmor

code injection via template expansion Warning

code injection via template expansion
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,10 @@ cs: cs-install
cs-fix: cs-install
XDEBUG_MODE=off php build-cs/vendor/bin/phpcbf

PHPSTAN_MEMORY_LIMIT ?= 450M

phpstan:
php bin/phpstan clear-result-cache -q && php -d memory_limit=450M bin/phpstan -v
php bin/phpstan clear-result-cache -q && php -d memory_limit=$(PHPSTAN_MEMORY_LIMIT) bin/phpstan -v

phpstan-result-cache:
php -d memory_limit=450M bin/phpstan
Expand Down
16 changes: 12 additions & 4 deletions build/composer-dependency-analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
'nette/php-generator',
];

$unknownClasses = [
'JetBrains\PhpStorm\Pure', // not present on composer's classmap
'PHPStan\ExtensionInstaller\GeneratedConfig', // generated
];
if (!class_exists('PHPStanTurbo\Runtime')) {
// provided by the optional phpstan_turbo extension (turbo-ext/); the ignore
// must be conditional — on machines with the extension loaded the class
// exists and a static ignore would be reported as unused
$unknownClasses[] = 'PHPStanTurbo\Runtime';
}

return $config
->addPathToScan(__DIR__ . '/../bin', true)
->ignoreErrorsOnPackages(
Expand All @@ -35,7 +46,4 @@
->ignoreErrorsOnPackage('phpunit/phpunit', [ErrorType::DEV_DEPENDENCY_IN_PROD]) // prepared test tooling
->ignoreErrorsOnPackage('jetbrains/phpstorm-stubs', [ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV]) // there is no direct usage, but we need newer version then required by ondrejmirtes/BetterReflection
->ignoreErrorsOnPath(__DIR__ . '/../tests', [ErrorType::UNKNOWN_CLASS, ErrorType::UNKNOWN_FUNCTION, ErrorType::SHADOW_DEPENDENCY]) // to be able to test invalid symbols
->ignoreUnknownClasses([
'JetBrains\PhpStorm\Pure', // not present on composer's classmap
'PHPStan\ExtensionInstaller\GeneratedConfig', // generated
]);
->ignoreUnknownClasses($unknownClasses);
2 changes: 2 additions & 0 deletions build/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ parameters:
- ../tests
bootstrapFiles:
- ../tests/phpstan-bootstrap.php
scanFiles:
- ../turbo-ext/analysis-stub.php
cache:
nodesByStringCountMax: 128
checkUninitializedProperties: true
Expand Down
1 change: 0 additions & 1 deletion conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ includes:
- parametersSchema.neon
- services.neon
- parsers.neon
- turbo.neon

parameters:
bootstrapFiles:
Expand Down
7 changes: 0 additions & 7 deletions conf/turbo.neon

This file was deleted.

8 changes: 7 additions & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ parameters:
-
rawMessage: Casting to string something that's already string.
identifier: cast.useless
count: 3
count: 1
path: src/Analyser/MutatingScope.php

-
Expand Down Expand Up @@ -114,6 +114,12 @@ parameters:
count: 1
path: src/Analyser/RuleErrorTransformer.php

-
rawMessage: Casting to string something that's already string.
identifier: cast.useless
count: 2
path: src/Analyser/ScopeOps.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantBooleanType is error-prone and deprecated. Use Type::isTrue() or Type::isFalse() instead.'
identifier: phpstanApi.instanceofType
Expand Down
7 changes: 6 additions & 1 deletion src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,12 @@ public function processAssignVar(
}

$truthyType = TypeCombinator::removeFalsey($type);
if ($truthyType !== $type) {
// Value comparison, not identity: remove() happens to hand back the very same
// instance when it removes nothing, but that is not part of its contract — the
// falsey loop below already compares with equals(). The identity check is only
// a fast path (equals() has no such shortcut, and no-op removal is the common
// case here).
if ($truthyType !== $type && !$truthyType->equals($type)) {
$truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createTruthy());
$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType, $impurePoints, $assignedExpr);
$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType, $impurePoints, $assignedExpr);
Expand Down
Loading
Loading