Skip to content

Php modernisation#529

Merged
dimitris-m merged 12 commits intomainfrom
php-modernisation
Jan 9, 2026
Merged

Php modernisation#529
dimitris-m merged 12 commits intomainfrom
php-modernisation

Conversation

@corneliuhoffman
Copy link
Contributor

@corneliuhoffman corneliuhoffman commented Jan 8, 2026

PHP 7.1 - 8.4 Syntax Support

Adds comprehensive PHP syntax support to the Menhir parser. Closes #297.


b0529bc - added tests

Initial test files for PHP 8.x features.


d3da661 - readonly fix

PHP 8.2: Readonly Classes

  readonly class ImmutablePoint {
      public int $x;
      public int $y;
  }

18aa6ce - Union and Intersection DNF

PHP 8.2: DNF Types (Disjunctive Normal Form)

  function process((Countable&Iterator)|null $input): (A&B)|C {
      // ...
  }

b59447d - property hooks

PHP 8.4: Property Hooks

  class User {
      // Block syntax
      public string $name {
          get { return $this->name; }
          set { $this->name = strtoupper($value); }
      }

      // Arrow syntax
      public int $age {
          get => $this->age;
          set => max(0, $value);
      }
  }

aaed1fb - asymmetric visibility private(set) and protected(set)

PHP 8.4: Asymmetric Visibility

  class Counter {
      public private(set) int $count = 0;
      public protected(set) string $name = "";
  }

19e3d19 - few others

PHP 7.1: Class Constant Visibility

  class Config {
      public const PUBLIC_CONST = 1;
      protected const PROTECTED_CONST = 2;
      private const PRIVATE_CONST = 3;
  }

PHP 7.1: Multi-Catch Exception Handling

  try {
      riskyOperation();
  } catch (InvalidArgumentException | TypeError | ValueError $e) {
      handleError($e);
  }

PHP 8.3: Dynamic Class Constant Fetch

  $name = 'VERSION';
  echo Config::{$name};

e09be54 - first-class callable syntax

PHP 8.1: First-Class Callable Syntax

  $fn = strlen(...);           // → Closure::fromCallable('strlen')
  $fn = $obj->method(...);     // → Closure::fromCallable([$obj, 'method'])
  $fn = Foo::bar(...);         // → Closure::fromCallable([Foo::class, 'bar'])
```  In semgrep patterns, ... retains its ellipsis meaning.

Comment on lines +5 to +11
public string $name {
get {
return $this->name;
}
set {
$this->name = strtoupper($value);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend to add a tainting test also for this material. Where $value is tainted for example.

Copy link
Collaborator

@dimitris-m dimitris-m Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular I'm wondering how it relates to #516 and #525.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

message: Detected first-class callable (desugared to Closure::fromCallable)
languages: [php]
severity: INFO
pattern: Closure::fromCallable(...)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document that this is the only way to match such constructs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in Wiki?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add a php upgrade in wiki

@maciejpirog
Copy link
Contributor

Property hooks don't produce correct AST. Example from https://www.php.net/manual/en/language.oop5.property-hooks.php

<?php
class Example
{
    private bool $modified = false;

    public string $foo = 'default value' {
        get {
            if ($this->modified) {
                return $this->foo . ' (modified)';
            }
            return $this->foo;
        }
        set(string $value) {
            $this->foo = strtolower($value);
            $this->modified = true;
        }
    }
}

$example = new Example();
$example->foo = 'changed';
print $example->foo;
?>

The AST for $foo is:

DefStmt(
({
    name=EN(
            Id(("$foo", ()),
            {id_flags=Ref(0); 
            id_resolved_alternative=Ref([]); 
            id_resolved=Ref(Some((EnclosedVar, 2))); 
            id_type=Ref(Some({t_attrs=[]; 
                                t=TyN(
                                    Id(("string", ()),
                                    {id_flags=Ref(
                                        2); 
                                        id_resolved_alternative=Ref(
                                        []); 
                                        id_resolved=Ref(
                                        None); 
                                        id_type=Ref(
                                        None); 
                                        id_svalue=Ref(
                                        None); }));
                                }));
            id_svalue=Ref(None); }));
    attrs=[KeywordAttr((Public, ()))]; tparams=None; },
    VarDef(
    {
    vinit=Some(ArrayAccess(
                    L(String(("default value", ()))),
                    ArrayAccess(
                    Call(
                        ArrayAccess(
                        N(
                            Id(("get", ()),
                            {id_flags=Ref(2); 
                            id_resolved_alternative=Ref(
                            []); id_resolved=Ref(
                            None); id_type=Ref(None); 
                            id_svalue=Ref(None); })),
                        DotAccess(
                            ArrayAccess(
                            Call(
                                N(
                                Id(("if", ()),
                                    {id_flags=Ref(2); 
                                    id_resolved_alternative=Ref(
                                    []); 
                                    id_resolved=Ref(
                                    None); 
                                    id_type=Ref(None); 
                                    id_svalue=Ref(None); })),
                                [Arg(
                                DotAccess(
                                    IdSpecial((This, ())),
                                    (),
                                    FN(
                                    Id(("modified", ()),
                                        {id_flags=Ref(
                                        0); 
                                        id_resolved_alternative=Ref(
                                        []); 
                                        id_resolved=Ref(
                                        None); 
                                        id_type=Ref(
                                        None); 
                                        id_svalue=Ref(
                                        None); }))))]),
                            Call(
                                IdSpecial((Op(Concat), ())),
                                [Arg(
                                DotAccess(
                                    IdSpecial((This, ())),
                                    (),
                                    FN(
                                    Id(("foo", ()),
                                        {id_flags=Ref(
                                        0); 
                                        id_resolved_alternative=Ref(
                                        []); 
                                        id_resolved=Ref(
                                        None); 
                                        id_type=Ref(
                                        None); 
                                        id_svalue=Ref(
                                        None); }))));
                                Arg(
                                L(
                                    String(
                                    (" (modified)", ()))))])),
                            (),
                            FN(
                            Id(("foo", ()),
                                {id_flags=Ref(0); 
                                id_resolved_alternative=Ref(
                                []); id_resolved=Ref(
                                None); id_type=Ref(
                                None); id_svalue=Ref(
                                None); })))),
                        [Arg(
                        N(
                            Id(("$value", ()),
                            {id_flags=Ref(2); 
                                id_resolved_alternative=Ref(
                                []); id_resolved=Ref(
                                None); id_type=Ref(None); 
                                id_svalue=Ref(None); })))]),
                    Assign(
                        DotAccess(IdSpecial((This, ())), (),
                        FN(
                            Id(("modified", ()),
                            {id_flags=Ref(0); 
                            id_resolved_alternative=Ref(
                            []); id_resolved=Ref(
                            None); id_type=Ref(None); 
                            id_svalue=Ref(None); }))),
                        (), L(Bool((true, ())))))));
    vtype=Some({t_attrs=[]; 
                t=TyN(
                    Id(("string", ()),
                        {id_flags=Ref(2); 
                        id_resolved_alternative=Ref(
                        []); id_resolved=Ref(None); 
                        id_type=Ref(None); 
                        id_svalue=Ref(None); }));
                });
    vtok=None; })))

It seems that things in { ... } are not even parsed as statements (and we get weird things like Call(N(Id(("if", ()),...))))

@corneliuhoffman
Copy link
Contributor Author

Property hooks don't produce correct AST. Example from https://www.php.net/manual/en/language.oop5.property-hooks.php
whoa, thanks

@corneliuhoffman corneliuhoffman force-pushed the php-modernisation branch 2 times, most recently from a9f9554 to abec309 Compare January 9, 2026 11:57
@corneliuhoffman
Copy link
Contributor Author

For some reason I cannot comment on @maciejpirog comment above. Now the thing parses fine and you can see a tainting test as well

@dimitris-m
Copy link
Collaborator

For some reason I cannot comment on @maciejpirog comment above. Now the thing parses fine and you can see a tainting test as well

I think because it was his review comment, but it's possible to "quote reply".

Copy link
Collaborator

@dimitris-m dimitris-m left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - modulo small comments

@maciejpirog
Copy link
Contributor

The AST looks good now! One thing to consider in future is the naming of the functions that represent setters and getters. They are now always called get and set, so there could be some confusion if we have more than one property.

I don't think it's urgent, because, as I understand it, at the moment we don't use properties in intrafile tainting, and whenever we have obj->prop = 2, it is treated as a field rather than being translated to a property call. But to support this would require a more global refactor, so not needed for this PR.

@dimitris-m
Copy link
Collaborator

The AST looks good now! One thing to consider in future is the naming of the functions that represent setters and getters. They are now always called get and set, so there could be some confusion if we have more than one property.

I don't think it's urgent, because, as I understand it, at the moment we don't use properties in intrafile tainting, and whenever we have obj->prop = 2, it is treated as a field rather than being translated to a property call. But to support this would require a more global refactor, so not needed for this PR.

Maybe open an issue for this? So we don't forget.

@dimitris-m dimitris-m merged commit 0e2e4e9 into main Jan 9, 2026
6 checks passed
@dimitris-m dimitris-m deleted the php-modernisation branch January 9, 2026 18:01
@dimitris-m dimitris-m added the lang Add or improve language support label Jan 11, 2026
@maciejpirog maciejpirog mentioned this pull request Jan 14, 2026
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Jan 17, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [opengrep/opengrep](https://github.com/opengrep/opengrep) | minor | `v1.14.1` → `v1.15.1` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>opengrep/opengrep (opengrep/opengrep)</summary>

### [`v1.15.1`](https://github.com/opengrep/opengrep/releases/tag/v1.15.1): Opengrep 1.15.1

[Compare Source](opengrep/opengrep@v1.15.0...v1.15.1)

#### Bug fixes

- Clojure translation improvements by [@&#8203;dimitris-m](https://github.com/dimitris-m) in [#&#8203;534](opengrep/opengrep#534)

**Full Changelog**: <opengrep/opengrep@v1.15.0...v1.15.1>

### [`v1.15.0`](https://github.com/opengrep/opengrep/releases/tag/v1.15.0): Opengrep 1.15.0

[Compare Source](opengrep/opengrep@v1.14.1...v1.15.0)

#### What's Changed

- Clojure translation part III by [@&#8203;dimitris-m](https://github.com/dimitris-m) in [#&#8203;527](opengrep/opengrep#527)
- Php modernisation by [@&#8203;corneliuhoffman](https://github.com/corneliuhoffman) in [#&#8203;529](opengrep/opengrep#529)
- Intrafile tainting with variadic functions by [@&#8203;maciejpirog](https://github.com/maciejpirog) in [#&#8203;538](opengrep/opengrep#538)
- C#: The `field` implicit parameter can be skipped in a pattern by [@&#8203;maciejpirog](https://github.com/maciejpirog) in [#&#8203;525](opengrep/opengrep#525)
- C#: Add conditional array access (`?[...]`) to l-values by [@&#8203;maciejpirog](https://github.com/maciejpirog) in [#&#8203;535](opengrep/opengrep#535)
- C#: Collection expressions vs attributes with targets (parser fix) by [@&#8203;maciejpirog](https://github.com/maciejpirog) in [#&#8203;539](opengrep/opengrep#539)
- Add `noopengrep` to the default nosem patterns by [@&#8203;dimitris-m](https://github.com/dimitris-m) in [#&#8203;533](opengrep/opengrep#533)

**Full Changelog**: <opengrep/opengrep@v1.14.1...v1.15.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi44MS4yIiwidXBkYXRlZEluVmVyIjoiNDIuODIuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6Om1pbm9yIl19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lang Add or improve language support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PHP 5->Php 8.4 parser

3 participants