Skip to content

[php2cpg] feat: support scope resolution operators#5782

Open
TNSelahle wants to merge 5 commits intomasterfrom
tebogo/php-improvements
Open

[php2cpg] feat: support scope resolution operators#5782
TNSelahle wants to merge 5 commits intomasterfrom
tebogo/php-improvements

Conversation

@TNSelahle
Copy link
Member

@TNSelahle TNSelahle commented Jan 26, 2026

Add support for static and parent class scope resolution operators.

Relates to https://github.com/ShiftLeftSecurity/codescience/issues/8640

@TNSelahle TNSelahle self-assigned this Jan 26, 2026
}

"have 'this' as the call receiver" in {
inside(cpg.call("bar").receiver.isIdentifier.l) { case (identifier: Identifier) :: Nil =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Please check that the this argument has a REF edge to the this parameter of the enclosing method

}
}

"contain <staticReceiver> as argument 0 of static functions" in {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"contain <staticReceiver> as argument 0 of static functions" in {
"contain <staticReceiver> as parameter 0 of static functions" in {

Copy link
Contributor

Choose a reason for hiding this comment

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

Just a nit pick

Comment on lines +405 to +406
identifier.name shouldBe NameConstants.StaticReceiver
identifier.code shouldBe NameConstants.Static
Copy link
Contributor

@johannescoetzee johannescoetzee Jan 26, 2026

Choose a reason for hiding this comment

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

Are we going to add special support for this to the dataflow engine? I could be missing something, but it looks like the <staticReceiver> call receiver will be a dead-end for dataflow tracking as it is currently

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, but the details I discussed with @ml86 around this are escaping me :D. I'm gonna chat about it in my 1-on-1 with him later today.

Copy link
Contributor

Choose a reason for hiding this comment

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

<staticReceiver> is from the data flow engines perspective just a normal parameter and static:: calls are normal dynamic call sites. This is just a big fat misnomer from the PHP people.

Comment on lines +414 to +417
fooMethod.parameter.headOption.map(_.name) shouldBe Some(NameConstants.StaticReceiver)

barMethod.name shouldBe "bar"
barMethod.parameter.headOption.map(_.name) shouldBe Some(NameConstants.StaticReceiver)
Copy link
Contributor

Choose a reason for hiding this comment

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

First parameter returned by .parameter step is not guaranteed to be the one with order 0. You have to filter for that.

inside(cpg.call("bar").l) { case (call: Call) :: Nil =>
call.staticReceiver shouldBe Some("Foo")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

<staticReceiver> parameter of foo needs to be argument 0 to bar call. At least the check for that is missing.

inside(cpg.call("bar").l) { case (call: Call) :: Nil =>
call.staticReceiver shouldBe Some("Foo")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

$this parameter of foo needs to be argument 0 to bar call. At least the check for that is missing.

inside(cpg.call("bar").l) { case (call: Call) :: Nil =>
call.staticReceiver shouldBe Some("Foo")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

$this parameter of foo needs to be argument 0 to bar call. At least the check for that is missing.

inside(cpg.call("bar").l) { case (call: Call) :: Nil =>
call.staticReceiver shouldBe Some("Base")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Like above the argument 0 check is missing.

inside(cpg.call("bar").l) { case (call: Call) :: Nil =>
call.staticReceiver shouldBe Some("Base")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Like above the argument 0 check is missing.

inside(cpg.call("bar").l) { case (call: Call) :: Nil =>
call.staticReceiver shouldBe Some("Base")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Like above the argument 0 check is missing.

inside(cpg.call("bar").l) { case (call: Call) :: Nil =>
call.staticReceiver shouldBe Some("Base")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Like above the argument 0 check is missing.

}
}

"have the correct receivers" in {
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and in all the other tests above: Please change the test name to "have the correct staticReceiver property."


inside(call.receiver.isIdentifier.l) { case (identifier: Identifier) :: Nil =>
identifier.name shouldBe NameConstants.StaticReceiver
identifier.code shouldBe NameConstants.Static
Copy link
Contributor

Choose a reason for hiding this comment

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

For the slightly extended example

<?php
class Foo {
  public static function foo($test) {
    static::bar($test);
  }

  private static function bar($value) { echo $value; }
}

Foo::foo("hello");

my concern is how we get the type of <staticReceiver> from the Foo::foo call. I spoke @TNSelahle and I did miss the fact that static functions now have the <staticReceiver> parameter 0, but I don't think we're adding a corresponding argument at the call site. We have the STATIC_RECEIVER field which we could use for this, but we'd need to add special backend handling for that, as far as I am aware.

Copy link
Contributor

Choose a reason for hiding this comment

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

In this example we use TYPE_REF Foo as argument 0 for the Foo::foo call.

Copy link
Contributor

Choose a reason for hiding this comment

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

That is missing from this PR (foo only has the literal argument). @TNSelahle could you please add a testcase for the static call arguments, along with providing the type ref for that?

@TNSelahle TNSelahle force-pushed the tebogo/php-improvements branch 2 times, most recently from 44106fb to f672d27 Compare February 10, 2026 12:31
@TNSelahle TNSelahle force-pushed the tebogo/php-improvements branch from f672d27 to 87723f5 Compare February 18, 2026 14:37
@TNSelahle TNSelahle force-pushed the tebogo/php-improvements branch from 87723f5 to 2874a68 Compare February 23, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants