diff --git a/tests/TaintTest.php b/tests/TaintTest.php index 9ee55d4a1..42366bd10 100644 --- a/tests/TaintTest.php +++ b/tests/TaintTest.php @@ -7,1891 +7,1221 @@ use Psalm\Context; class TaintTest extends TestCase { /** + * @dataProvider providerValidCodeParse + * + * @param string $code + * * @return void */ - public function testTaintedInputFromMethodReturnTypeSimple() + public function testValidCode(string $code) { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); + $test_name = $this->getTestName(); + if (\strpos($test_name, 'SKIPPED-') !== false) { + $this->markTestSkipped('Skipped due to a bug.'); + } + + $file_path = self::$src_dir_path . 'somefile.php'; + + $this->addFile( + $file_path, + $code + ); $this->project_analyzer->trackTaintedInputs(); - $this->addFile( - 'somefile.php', - 'getUserId(); - } - - public function deleteUser(PDO $pdo) : void { - $userId = $this->getAppendedUserId(); - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); + $this->analyzeFile($file_path, new Context(), false); } /** + * @dataProvider providerInvalidCodeParse + * + * @param string $code + * @param string $error_message + * * @return void */ - public function testTaintedInputFromFunctionReturnType() + public function testInvalidCode(string $code, string $error_message) { + if (\strpos($this->getTestName(), 'SKIPPED-') !== false) { + $this->markTestSkipped(); + } + $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput - somefile.php:6:22 - Detected tainted html in path: $_GET -> $_GET[\'name\'] (somefile.php:3:28) -> getname (somefile.php:6:22) -> call to echo (somefile.php:6:22) -> echo#1'); + $this->expectExceptionMessageRegExp('/\b' . \preg_quote($error_message, '/') . '\b/'); + + $file_path = self::$src_dir_path . 'somefile.php'; + + $this->addFile( + $file_path, + $code + ); $this->project_analyzer->trackTaintedInputs(); - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); + $this->analyzeFile($file_path, new Context(), false); } /** - * @return void + * @return array */ - public function testTaintedInputFromExplicitTaintSource() + public function providerValidCodeParse() { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); + return [ + 'taintedInputInCreatedArrayNotEchoed' => [ + 'project_analyzer->trackTaintedInputs(); + $data = ["name" => $name, "id" => $id]; - $this->addFile( - 'somefile.php', - '" . htmlentities($data["name"]) . ""; + echo "

" . $data["id"] . "

";' + ], + 'taintedInputInAssignedArrayNotEchoed' => [ + 'analyzeFile('somefile.php', new Context()); + echo "

" . htmlentities($data["name"]) . "

"; + echo "

" . $data["id"] . "

";' + ], + 'taintedInputDirectlySuppressed' => [ + 'exec("delete from users where user_id = " . $userId); + } + }' + ], + 'taintedInputDirectlySuppressedWithOtherUse' => [ + 'exec("delete from users where user_id = " . $userId); + } + + public function deleteUserSafer(PDOWrapper $pdo) : void { + $userId = $this->getSafeId(); + $pdo->exec("delete from users where user_id = " . $userId); + } + + public function getSafeId() : string { + return "5"; + } + } + + class PDOWrapper { + /** + * @psalm-taint-sink sql $sql + */ + public function exec(string $sql) : void {} + }' + ], + 'taintedInputToParamButSafe' => [ + 'deleteUser( + $pdo, + $this->getAppendedUserId((string) $_GET["user_id"]) + ); + } + + public function getAppendedUserId(string $user_id) : string { + return "aaa" . $user_id; + } + + public function deleteUser(PDO $pdo, string $userId) : void { + $userId2 = strlen($userId); + $pdo->exec("delete from users where user_id = " . $userId2); + } + }' + ], + 'ValidatedInputFromParam' => [ + 'getUserId(); + validateUserId($userId); + $this->deleteUser($pdo, $userId); + } + + public function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + }' + ], + 'untaintedInput' => [ + 'getUserId(); + } + + public function deleteUser(PDO $pdo) : void { + $userId = $this->getAppendedUserId(); + $pdo->exec("delete from users where user_id = " . $userId); + } + }' + ], + 'specializedCoreFunctionCall' => [ + ' [ + ' [ + ' [ + 's = (string) $_GET["FOO"]; + } + } + + class V1 extends V { + public function foo(O1 $o) : void { + echo U::shorten($o->s); + } + }' + ], + 'taintOnPregReplaceCallRemovedInFunction' => [ + 's = (string) $_GET["FOO"]; + } + } + + class V1 extends V { + public function foo(O1 $o) : void { + echo U::shorten($o->s); + } + }' + ], + 'taintOnStrReplaceCallRemovedInline' => [ + 's = (string) $_GET["FOO"]; + } + } + + class V1 extends V { + public function foo(O1 $o) : void { + /** + * @psalm-taint-escape html + */ + $a = str_replace("foo", "bar", $o->s); + echo $a; + } + }' + ], + 'NoTaintsOnSimilarPureCall' => [ + 's = $s; + } + } + + class O2 { + public string $t; + + public function __construct() { + $this->t = (string) $_GET["FOO"]; + } + } + + class V1 { + public function foo() : void { + $o = new O1((string) $_GET["FOO"]); + echo U::escape(U::shorten($o->s)); + } + } + + class V2 { + public function foo(O2 $o) : void { + echo U::shorten(U::escape($o->t)); + } + }' + ], + 'taintPropertyPassingObjectWithDifferentValue' => [ + 'id = $userId; + } + } + + class UserUpdater { + public static function doDelete(PDO $pdo, User $user) : void { + self::deleteUser($pdo, $user->name); + } + + public static function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + } + + $userObj = new User((string) $_GET["user_id"]); + UserUpdater::doDelete(new PDO(), $userObj);' + ], + 'taintPropertyWithoutPassingObject' => [ + 'id = $userId; + } + } + + class UserUpdater { + public static function doDelete(PDO $pdo, User $user) : void { + self::deleteUser($pdo, $user->id); + } + + public static function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + } + + $userObj = new User((string) $_GET["user_id"]);', + ], + 'specializeStaticMethod' => [ + 'expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); + return [ + 'taintedInputFromMethodReturnTypeSimple' => [ + 'project_analyzer->trackTaintedInputs(); + public function getAppendedUserId() : string { + return "aaaa" . $this->getUserId(); + } - $this->addFile( - 'somefile.php', - 'getAppendedUserId(); + $pdo->exec("delete from users where user_id = " . $userId); + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputFromFunctionReturnType' => [ + ' 'TaintedInput - src/somefile.php:6:26 - Detected tainted html in path: $_GET -> $_GET[\'name\'] (src/somefile.php:3:32) -> getname (src/somefile.php:6:26) -> call to echo (src/somefile.php:6:26) -> echo#1', + ], + 'taintedInputFromExplicitTaintSource' => [ + 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromGetArray() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromReturnToInclude() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromReturnToEval() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromReturnTypeToEcho() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'getUserId(); - } - - public function deleteUser(PDO $pdo) : void { - $userId = $this->getAppendedUserId(); - echo $userId; - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputInCreatedArrayNotEchoed() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - ' $name, "id" => $id]; - - echo "

" . htmlentities($data["name"]) . "

"; - echo "

" . $data["id"] . "

";' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputInCreatedArrayIsEchoed() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - ' $name]; - - echo "

" . $data["name"] . "

";' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputInAssignedArrayNotEchoed() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - '" . htmlentities($data["name"]) . ""; - echo "

" . $data["id"] . "

";' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputInAssignedArrayIsEchoed() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - '" . $data["name"] . "";' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputDirectly() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputDirectlySuppressed() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputDirectlySuppressedWithOtherUse() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - ' 'TaintedInput', + ], + 'taintedInputFromExplicitTaintSourceStaticMethod' => [ + 'exec("delete from users where user_id = " . $userId); + public static function getName() : string { + return ""; + } } - public function deleteUserSafer(PDOWrapper $pdo) : void { - $userId = $this->getSafeId(); - $pdo->exec("delete from users where user_id = " . $userId); + + echo Request::getName();', + 'error_message' => 'TaintedInput', + ], + 'taintedInputFromGetArray' => [ + 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromReturnTypeWithBranch() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'getUserId(); - - if (rand(0, 1)) { - $userId .= "aaa"; - } else { - $userId .= "bb"; + echo $name;', + 'error_message' => 'TaintedInput', + ], + 'taintedInputFromReturnToInclude' => [ + ' 'TaintedInput', + ], + 'taintedInputFromReturnToEval' => [ + ' 'TaintedInput', + ], + 'taintedInputFromReturnTypeToEcho' => [ + 'getUserId(); + } + + public function deleteUser(PDO $pdo) : void { + $userId = $this->getAppendedUserId(); + echo $userId; + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputInCreatedArrayIsEchoed' => [ + ' $name]; + + echo "

" . $data["name"] . "

";', + 'error_message' => 'TaintedInput', + ], + 'testTaintedInputInAssignedArrayIsEchoed' => [ + '" . $data["name"] . "";', + 'error_message' => 'TaintedInput', + ], + 'taintedInputDirectly' => [ + 'exec("delete from users where user_id = " . $userId); + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputFromReturnTypeWithBranch' => [ + 'getUserId(); + + if (rand(0, 1)) { + $userId .= "aaa"; + } else { + $userId .= "bb"; + } + + return $userId; + } + + public function deleteUser(PDO $pdo) : void { + $userId = $this->getAppendedUserId(); + $pdo->exec("delete from users where user_id = " . $userId); + } + }', + 'error_message' => 'TaintedInput', + ], + 'sinkAnnotation' => [ + 'getUserId(); + } + + public function deleteUser(PDOWrapper $pdo) : void { + $userId = $this->getAppendedUserId(); + $pdo->exec("delete from users where user_id = " . $userId); + } } - public function deleteUser(PDO $pdo) : void { - $userId = $this->getAppendedUserId(); - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); + class PDOWrapper { + /** + * @psalm-taint-sink sql $sql + */ + public function exec(string $sql) : void {} + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputFromParam' => [ + 'analyzeFile('somefile.php', new Context()); - } + public function getAppendedUserId() : string { + return "aaaa" . $this->getUserId(); + } - /** - * @return void - */ - public function testSinkAnnotation() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); + public function doDelete(PDO $pdo) : void { + $userId = $this->getAppendedUserId(); + $this->deleteUser($pdo, $userId); + } - $this->project_analyzer->trackTaintedInputs(); + public function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + }', + 'error_message' => 'TaintedInput - src/somefile.php:17:40 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (src/somefile.php:4:45) -> A::getUserId (src/somefile.php:3:55) -> concat (src/somefile.php:8:36) -> A::getAppendedUserId (src/somefile.php:7:63) -> $userId (src/somefile.php:12:29) -> call to A::deleteUser (src/somefile.php:13:53) -> A::deleteUser#2 (src/somefile.php:16:69) -> concat (src/somefile.php:17:40) -> call to PDO::exec (src/somefile.php:17:40) -> PDO::exec#1', + ], + 'taintedInputToParam' => [ + 'deleteUser( + $pdo, + $this->getAppendedUserId((string) $_GET["user_id"]) + ); + } - $this->addFile( - 'somefile.php', - 'getUserId(); - } + public function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputToParamAfterAssignment' => [ + 'deleteUser( + $pdo, + $this->getAppendedUserId((string) $_GET["user_id"]) + ); + } - public function deleteUser(PDOWrapper $pdo) : void { - $userId = $this->getAppendedUserId(); - $pdo->exec("delete from users where user_id = " . $userId); - } - } + public function getAppendedUserId(string $user_id) : string { + return "aaa" . $user_id; + } - class PDOWrapper { - /** - * @psalm-taint-sink sql $sql - */ - public function exec(string $sql) : void {} - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromParam() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput - somefile.php:17:36 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (somefile.php:4:41) -> A::getUserId (somefile.php:3:51) -> concat (somefile.php:8:32) -> A::getAppendedUserId (somefile.php:7:59) -> $userId (somefile.php:12:25) -> call to A::deleteUser (somefile.php:13:49) -> A::deleteUser#2 (somefile.php:16:65) -> concat (somefile.php:17:36) -> call to PDO::exec (somefile.php:17:36) -> PDO::exec#1'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'getUserId(); - } - - public function doDelete(PDO $pdo) : void { - $userId = $this->getAppendedUserId(); - $this->deleteUser($pdo, $userId); - } - - public function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputToParam() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'deleteUser( - $pdo, - $this->getAppendedUserId((string) $_GET["user_id"]) - ); - } - - public function getAppendedUserId(string $user_id) : string { - return "aaa" . $user_id; - } - - public function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputToParamAfterAssignment() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'deleteUser( - $pdo, - $this->getAppendedUserId((string) $_GET["user_id"]) - ); - } - - public function getAppendedUserId(string $user_id) : string { - return "aaa" . $user_id; - } - - public function deleteUser(PDO $pdo, string $userId) : void { - $userId2 = $userId; - $pdo->exec("delete from users where user_id = " . $userId2); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputToParamButSafe() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'deleteUser( - $pdo, - $this->getAppendedUserId((string) $_GET["user_id"]) - ); - } - - public function getAppendedUserId(string $user_id) : string { - return "aaa" . $user_id; - } - - public function deleteUser(PDO $pdo, string $userId) : void { - $userId2 = strlen($userId); - $pdo->exec("delete from users where user_id = " . $userId2); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputToParamAlternatePath() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput - somefile.php:23:40 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (somefile.php:7:63) -> call to A::getAppendedUserId (somefile.php:7:54) -> A::getAppendedUserId#1 (somefile.php:11:62) -> concat (somefile.php:12:32) -> A::getAppendedUserId (somefile.php:11:37) -> call to A::deleteUser (somefile.php:7:29) -> A::deleteUser#3 (somefile.php:19:81) -> concat (somefile.php:23:40) -> call to PDO::exec (somefile.php:23:40) -> PDO::exec#1'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'deleteUser( - $pdo, - self::doFoo(), - $this->getAppendedUserId((string) $_GET["user_id"]) - ); - } - - public function getAppendedUserId(string $user_id) : string { - return "aaa" . $user_id; - } - - public static function doFoo() : string { - return "hello"; - } - - public function deleteUser(PDO $pdo, string $userId, string $userId2) : void { - $pdo->exec("delete from users where user_id = " . $userId); - - if (rand(0, 1)) { + public function deleteUser(PDO $pdo, string $userId) : void { + $userId2 = $userId; $pdo->exec("delete from users where user_id = " . $userId2); } - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInParentLoader() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput - somefile.php:16:40 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (somefile.php:28:39) -> call to C::foo (somefile.php:28:30) -> C::foo#1 (somefile.php:23:48) -> call to AGrandChild::loadFull (somefile.php:24:47) -> AGrandChild::loadFull#1 (somefile.php:5:60) -> A::loadFull#1 (somefile.php:24:47) -> call to A::loadPartial (somefile.php:6:45) -> A::loadPartial#1 (somefile.php:3:72) -> AChild::loadPartial#1 (somefile.php:6:45) -> concat (somefile.php:16:40) -> call to PDO::exec (somefile.php:16:40) -> PDO::exec#1'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'exec("select * from foo where bar = " . $sink); - } - } - - class AGrandChild extends AChild {} - - class C { - public function foo(string $user_id) : void { - AGrandChild::loadFull($user_id); - } - } - - (new C)->foo((string) $_GET["user_id"]);' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testValidatedInputFromParam() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'getUserId(); - validateUserId($userId); - $this->deleteUser($pdo, $userId); - } - - public function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testUntaintedInput() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'getUserId(); - } - - public function deleteUser(PDO $pdo) : void { - $userId = $this->getAppendedUserId(); - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromProperty() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'userId = (string) $_GET["user_id"]; - } - - public function getAppendedUserId() : string { - return "aaaa" . $this->userId; - } - - public function doDelete(PDO $pdo) : void { - $userId = $this->getAppendedUserId(); - $this->deleteUser($pdo, $userId); - } - - public function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testSpecializedCoreFunctionCall() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputFromPropertyViaMixin() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'userId = (string) $_GET["user_id"]; - } - } - - /** @mixin A */ - class B { - private A $a; - - public function __construct(A $a) { - $this->a = $a; - } - - public function __get(string $name) { - return $this->a->$name; - } - } - - class C { - private B $b; - - public function __construct(B $b) { - $this->b = $b; - } - - public function getAppendedUserId() : string { - return "aaaa" . $this->b->userId; - } - - public function doDelete(PDO $pdo) : void { - $userId = $this->getAppendedUserId(); - $this->deleteUser($pdo, $userId); - } - - public function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputViaStaticFunction() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testTaintedInputViaPureStaticFunction() - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testUntaintedInputViaStaticFunctionWithSafePath() - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - /** - * @return void - */ - public function testUntaintedInputViaStaticFunctionWithoutSafePath() - { - $this->project_analyzer->trackTaintedInputs(); - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintedInputFromMagicProperty() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - ' */ - private $vars = []; - - public function __get(string $s) : string { - return $this->vars[$s]; - } - - public function __set(string $s, string $t) { - $this->vars[$s] = $t; - } - } - - function getAppendedUserId() : void { - $a = new A(); - $a->userId = (string) $_GET["user_id"]; - echo $a->userId; - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintOverMixed() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintStrConversion() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintHtmlEntities() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintIntoExec() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintIntoExecMultipleConcat() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintIntoNestedArrayUnnestedSeparately() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintIntoArrayAndThenOutAgain() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintAppendedToArray() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintOnSubstrCall() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 's = (string) $_GET["FOO"]; - } - } - - class V1 extends V { - public function foo(O1 $o) : void { - echo U::shorten($o->s); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintOnStrReplaceCallSimple() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 's = (string) $_GET["FOO"]; - } - } - - class V1 extends V { - public function foo(O1 $o) : void { - echo U::shorten($o->s); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintOnStrReplaceCallRemovedInFunction() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 's = (string) $_GET["FOO"]; - } - } - - class V1 extends V { - public function foo(O1 $o) : void { - echo U::shorten($o->s); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintOnPregReplaceCallRemovedInFunction() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 's = (string) $_GET["FOO"]; - } - } - - class V1 extends V { - public function foo(O1 $o) : void { - echo U::shorten($o->s); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintOnStrReplaceCallRemovedInline() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 's = (string) $_GET["FOO"]; - } - } - - class V1 extends V { - public function foo(O1 $o) : void { - /** - * @psalm-taint-escape html - */ - $a = str_replace("foo", "bar", $o->s); - echo $a; - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintOnPregReplaceCall() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 's = (string) $_GET["FOO"]; - } - } - - class V1 extends V { - public function foo(O1 $o) : void { - echo U::shorten($o->s); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testNoTaintsOnSimilarPureCall() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 's = $s; - } - } - - class O2 { - public string $t; - - public function __construct() { - $this->t = (string) $_GET["FOO"]; - } - } - - class V1 { - public function foo() : void { - $o = new O1((string) $_GET["FOO"]); - echo U::escape(U::shorten($o->s)); - } - } - - class V2 { - public function foo(O2 $o) : void { - echo U::shorten(U::escape($o->t)); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testIndirectGetAssignment() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'name = $name; - } - - /** - * @psalm-specialize-call - */ - public function getArg(string $method, string $type) - { - $arg = null; - - switch ($method) { - case "post": - if (isset($_POST[$this->name])) { - $arg = $_POST[$this->name]; - } - break; - - case "get": - if (isset($_GET[$this->name])) { - $arg = $_GET[$this->name]; - } - break; + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputToParamAlternatePath' => [ + 'deleteUser( + $pdo, + self::doFoo(), + $this->getAppendedUserId((string) $_GET["user_id"]) + ); } - return $this->filterInput($type, $arg); + public function getAppendedUserId(string $user_id) : string { + return "aaa" . $user_id; + } + + public static function doFoo() : string { + return "hello"; + } + + public function deleteUser(PDO $pdo, string $userId, string $userId2) : void { + $pdo->exec("delete from users where user_id = " . $userId); + + if (rand(0, 1)) { + $pdo->exec("delete from users where user_id = " . $userId2); + } + } + }', + 'error_message' => 'TaintedInput - src/somefile.php:23:44 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (src/somefile.php:7:67) -> call to A::getAppendedUserId (src/somefile.php:7:58) -> A::getAppendedUserId#1 (src/somefile.php:11:66) -> concat (src/somefile.php:12:36) -> A::getAppendedUserId (src/somefile.php:11:41) -> call to A::deleteUser (src/somefile.php:7:33) -> A::deleteUser#3 (src/somefile.php:19:85) -> concat (src/somefile.php:23:44) -> call to PDO::exec (src/somefile.php:23:44) -> PDO::exec#1', + ], + 'taintedInParentLoader' => [ + 'exec("select * from foo where bar = " . $sink); + } + } + + class AGrandChild extends AChild {} + + class C { + public function foo(string $user_id) : void { + AGrandChild::loadFull($user_id); + } + } + + (new C)->foo((string) $_GET["user_id"]);', + 'error_message' => 'TaintedInput - src/somefile.php:16:44 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (src/somefile.php:28:43) -> call to C::foo (src/somefile.php:28:34) -> C::foo#1 (src/somefile.php:23:52) -> call to AGrandChild::loadFull (src/somefile.php:24:51) -> AGrandChild::loadFull#1 (src/somefile.php:5:64) -> A::loadFull#1 (src/somefile.php:24:51) -> call to A::loadPartial (src/somefile.php:6:49) -> A::loadPartial#1 (src/somefile.php:3:76) -> AChild::loadPartial#1 (src/somefile.php:6:49) -> concat (src/somefile.php:16:44) -> call to PDO::exec (src/somefile.php:16:44) -> PDO::exec#1', + ], + 'taintedInputFromProperty' => [ + 'userId = (string) $_GET["user_id"]; + } + + public function getAppendedUserId() : string { + return "aaaa" . $this->userId; + } + + public function doDelete(PDO $pdo) : void { + $userId = $this->getAppendedUserId(); + $this->deleteUser($pdo, $userId); + } + + public function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputFromPropertyViaMixin' => [ + 'userId = (string) $_GET["user_id"]; + } + } + + /** @mixin A */ + class B { + private A $a; + + public function __construct(A $a) { + $this->a = $a; + } + + public function __get(string $name) { + return $this->a->$name; + } + } + + class C { + private B $b; + + public function __construct(B $b) { + $this->b = $b; + } + + public function getAppendedUserId() : string { + return "aaaa" . $this->b->userId; + } + + public function doDelete(PDO $pdo) : void { + $userId = $this->getAppendedUserId(); + $this->deleteUser($pdo, $userId); + } + + public function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintedInputViaStaticFunction' => [ + ' 'TaintedInput', + ], + 'taintedInputViaPureStaticFunction' => [ + ' 'TaintedInput', + ], + 'untaintedInputViaStaticFunctionWithoutSafePath' => [ + ' 'TaintedInput', + ], + 'taintedInputFromMagicProperty' => [ + ' */ + private $vars = []; + + public function __get(string $s) : string { + return $this->vars[$s]; + } + + public function __set(string $s, string $t) { + $this->vars[$s] = $t; + } + } + + function getAppendedUserId() : void { + $a = new A(); + $a->userId = (string) $_GET["user_id"]; + echo $a->userId; + }', + 'error_message' => 'TaintedInput', + ], + 'taintOverMixed' => [ + ' 'TaintedInput', + ], + 'taintStrConversion' => [ + ' 'TaintedInput', + ], + 'taintIntoExec' => [ + ' 'TaintedInput', + ], + 'taintIntoExecMultipleConcat' => [ + ' 'TaintedInput', + ], + 'taintIntoNestedArrayUnnestedSeparately' => [ + ' 'TaintedInput', + ], + 'taintIntoArrayAndThenOutAgain' => [ + ' 'TaintedInput', + ], + 'taintAppendedToArray' => [ + ' 'TaintedInput', + ], + 'taintOnSubstrCall' => [ + 's = (string) $_GET["FOO"]; + } + } + + class V1 extends V { + public function foo(O1 $o) : void { + echo U::shorten($o->s); + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintOnStrReplaceCallSimple' => [ + 's = (string) $_GET["FOO"]; + } + } + + class V1 extends V { + public function foo(O1 $o) : void { + echo U::shorten($o->s); + } + }', + 'error_message' => 'TaintedInput', + ], + 'taintOnPregReplaceCall' => [ + 's = (string) $_GET["FOO"]; + } + } + + class V1 extends V { + public function foo(O1 $o) : void { + echo U::shorten($o->s); + } + }', + 'error_message' => 'TaintedInput', + ], + 'IndirectGetAssignment' => [ + 'name = $name; + } + + /** + * @psalm-specialize-call + */ + public function getArg(string $method, string $type) + { + $arg = null; + + switch ($method) { + case "post": + if (isset($_POST[$this->name])) { + $arg = $_POST[$this->name]; + } + break; + + case "get": + if (isset($_GET[$this->name])) { + $arg = $_GET[$this->name]; + } + break; + } + + return $this->filterInput($type, $arg); + } + + protected function filterInput(string $type, $arg) + { + // input is null + if ($arg === null) { + return null; + } + + // set to null if sanitize clears arg + if ($arg === "") { + $arg = null; + } + + // type casting + if ($arg !== null) { + $arg = $this->typeCastInput($type, $arg); + } + + return $arg; + } + + protected function typeCastInput(string $type, $arg) { + if ($type === "string") { + return (string) $arg; + } + return null; } + } - // set to null if sanitize clears arg - if ($arg === "") { - $arg = null; + echo (new InputFilter("hello"))->getArg("get", "string");', + 'error_message' => 'TaintedInput', + ], + 'taintPropertyPassingObject' => [ + 'id = $userId; + } + } + + class UserUpdater { + public static function doDelete(PDO $pdo, User $user) : void { + self::deleteUser($pdo, $user->id); } - // type casting - if ($arg !== null) { - $arg = $this->typeCastInput($type, $arg); + public static function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } + } + + $userObj = new User((string) $_GET["user_id"]); + UserUpdater::doDelete(new PDO(), $userObj);', + 'error_message' => 'TaintedInput', + ], + 'taintPropertyPassingObjectSettingValueLater' => [ + 'id = $userId; } - return $arg; + public function setId(string $userId) : void { + $this->id = $userId; + } } - protected function typeCastInput(string $type, $arg) { - if ($type === "string") { - return (string) $arg; + class UserUpdater { + public static function doDelete(PDO $pdo, User $user) : void { + self::deleteUser($pdo, $user->id); } - return null; - } - } - - echo (new InputFilter("hello"))->getArg("get", "string");' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintPropertyPassingObject() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'id = $userId; - } - } - - class UserUpdater { - public static function doDelete(PDO $pdo, User $user) : void { - self::deleteUser($pdo, $user->id); + public static function deleteUser(PDO $pdo, string $userId) : void { + $pdo->exec("delete from users where user_id = " . $userId); + } } - public static function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - } + $userObj = new User("5"); + $userObj->setId((string) $_GET["user_id"]); + UserUpdater::doDelete(new PDO(), $userObj);', + 'error_message' => 'TaintedInput', + ], + 'ImplodeExplode' => [ + ' 'TaintedInput', + ], + 'taintThroughPregReplaceCallback' => [ + 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintPropertyPassingObjectSettingValueLater() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'id = $userId; - } - - public function setId(string $userId) : void { - $this->id = $userId; - } - } - - class UserUpdater { - public static function doDelete(PDO $pdo, User $user) : void { - self::deleteUser($pdo, $user->id); - } - - public static function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - } - - $userObj = new User("5"); - $userObj->setId((string) $_GET["user_id"]); - UserUpdater::doDelete(new PDO(), $userObj);' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintPropertyPassingObjectWithDifferentValue() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'id = $userId; - } - } - - class UserUpdater { - public static function doDelete(PDO $pdo, User $user) : void { - self::deleteUser($pdo, $user->name); - } - - public static function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - } - - $userObj = new User((string) $_GET["user_id"]); - UserUpdater::doDelete(new PDO(), $userObj);' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testTaintPropertyWithoutPassingObject() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'id = $userId; - } - } - - class UserUpdater { - public static function doDelete(PDO $pdo, User $user) : void { - self::deleteUser($pdo, $user->id); - } - - public static function deleteUser(PDO $pdo, string $userId) : void { - $pdo->exec("delete from users where user_id = " . $userId); - } - } - - $userObj = new User((string) $_GET["user_id"]);' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testImplodeExplode() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testSpecializeStaticMethod() : void - { - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintThroughPregReplaceCallback() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintedFunctionWithNoTypes() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintedStaticCallWithNoTypes() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - ' 'TaintedInput', + ], + 'taintedFunctionWithNoTypes' => [ + 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintedInstanceCallWithNoTypes() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - ' 'TaintedInput', + ], + 'taintedStaticCallWithNoTypes' => [ + 'rawinput();' - ); + echo A::rawinput();', + 'error_message' => 'TaintedInput', + ], + 'taintedInstanceCallWithNoTypes' => [ + 'analyzeFile('somefile.php', new Context()); - } + echo (new A())->rawinput();', + 'error_message' => 'TaintedInput', + ], + 'encapsulatedString' => [ + ' 'TaintedInput', + ], + 'namespacedFunction' => [ + 'expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); + function identity(string $s) : string { + return $s; + } - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testNamespacedFunction() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testTaintedInstancePrint() : void - { - $this->expectException(\Psalm\Exception\CodeException::class); - $this->expectExceptionMessage('TaintedInput - somefile.php:2:23 - Detected tainted html in path: $_GET -> $_GET[\'name\'] (somefile.php:2:23) -> call to print (somefile.php:2:23) -> print#1'); - - $this->project_analyzer->trackTaintedInputs(); - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); + echo identity($_GET[\'userinput\']);', + 'error_message' => 'TaintedInput', + ], + 'namespacedFunction' => [ + ' 'TaintedInput - src/somefile.php:2:27 - Detected tainted html in path: $_GET -> $_GET[\'name\'] (src/somefile.php:2:27) -> call to print (src/somefile.php:2:27) -> print#1', + ], + ]; } }