Skip to content

Commit

Permalink
move configuration-y bits to /generator/config
Browse files Browse the repository at this point in the history
  • Loading branch information
shish committed Feb 7, 2025
1 parent cfe191a commit 260c86c
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 163 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Safe-PHP code is generated automatically from the PHP doc.
figure out which functions are unsafe, and will generate safe wrappers
for them, which are then written to `generated/`. As a Safe-PHP developer,
you probably spend most time here.
* `generator/config/` has the rules for which functions are included or
excluded
* `generated/` will be deleted and regenerated from scratch as part of CI
runs - don't manually edit any of these files.
* `lib/` contains some special cases where automatic generation is tricky.
Expand Down
32 changes: 32 additions & 0 deletions generated/8.1/datetime.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions generated/8.1/functionsList.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions generated/8.1/rector-migrate.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions generated/8.4/datetime.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions generated/8.5/datetime.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions generator/config/detectEmptyFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

return function (string $text): bool {
if (preg_match('/an\s+empty\s+string\s+on\s+error/', $text)) {
return true;
}

return false;
};
43 changes: 43 additions & 0 deletions generator/config/detectFalsyFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

return function (string $text): bool {
$falsies = [
'/[Tt]he function returns &false;/m',
'/&false;\s+on\s+error/m',
'/&false;\s+on\s+failure/m',
'/&false;\s+if\s+an\s+error\s+occurred/m',
'/&return.success;/m',
'/&return.nullorfalse;/m',
'/&return.falseforfailure;/m',
'/&date.datetime.return.modifiedobjectorfalseforfailure;/m',
'/ or &false; \\(and generates an error/m',
'/&false;\s+if\s+the\s+number\s+of\s+elements\s+for\s+each\s+array\s+isn\'t\s+equal/m',
'/If\s+the\s+call\s+fails,\s+it\s+will\s+return\s+&false;/m',
'/Upon\s+failure,?\s+\<function\>[\w_]{1,15}?\<\/function\>\s+returns\s+&false;/m',
'/On\s+failure,\s+&false;\s+is\s+returned/m',
'/on\s+success,\s+otherwise\s+&false;\s+is\s+returned/m',
'/Returns.*on success[.\s\S]+Returns &false;\s+if/m',
'/&gd\.return\.identifier;/m',
'/If a non-numeric value is used for\s+\<parameter\>timestamp\<\/parameter\>, &false; is returned/m', // date
'/&false; is returned if\s+the image type is unsupported, the data is not in a recognised format,\s+or the image is corrupt and cannot be loaded/m', // imagecreatefromstring
"/&false; when the given class doesn't exist/m", // class_implements
"/&false; on failure/m" , // get_headers and ldap_search
"/&false; if a syntactically invalid/m", // inet_pton
"/&false; if the pipe\s+cannot be established/m", // shell_exec
"/&false; if an error occurs/m", // cfg_get_var
"/On failure\s+returns &false;./m", // proc_open
];
foreach ($falsies as $falsie) {
if (preg_match($falsie, $text)) {
return true;
}
}
if (preg_match('/&false;\s+otherwise/m', $text) && !preg_match('/(returns\s+&true;|&true;\s+on\s+success|&true;\s+if)/im', $text)) {
return true;
}
if (preg_match('/may\s+return\s+&false;/m', $text) && !preg_match('/(returns\s+&true;|&true;\s+on\s+success|&true;\s+if)/im', $text)) {
return true;
}

return false;
};
21 changes: 21 additions & 0 deletions generator/config/detectNullsyFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

return function (string $text): bool {
if (preg_match('/&null;\s+on\s+failure/', $text)) {
return true;
}

// used to detect old (8.1) versions of array_replace
if (preg_match('/&null;\s+if\s+an\s+error\s+occurs/', $text)) {
// skip a false positive for shell-exec (the docs mention that it
// "returns null if an error occurs" _in the subprocess_ OR if the
// subprocess returns nothing, and users should use `exec` if they
// actually care about error handling)
if (preg_match('/&null; if an error occurs or the program/', $text)) {
return false;
}
return true;
}

return false;
};
174 changes: 21 additions & 153 deletions generator/src/XmlDocParser/DocPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Safe\XmlDocParser;

use Safe\Generator\FileCreator;

use function explode;
use function strpos;

Expand Down Expand Up @@ -50,178 +52,44 @@ private function getIsDeprecated(string $file): bool
*/
public function detectFalsyFunction(): bool
{
$file = file_get_contents($this->path);
if ($file === false) {
throw new \RuntimeException('An error occurred while reading '.$this->path);
}

if ($this->getIsDeprecated($file)) {
return false;
}

// Only evaluate the text inside the `<refsect1 role="returnvalues">...</refsect1>` section of the doc page.
// This minimizes 'false positives', where text such as "returns false when ..." could be matched outside
// the function's dedicated Return Values section.
$returnValuesSection = $this->extractSection('returnvalues', $file);

if (preg_match('/[Tt]he function returns &false;/m', $returnValuesSection)) {
return true;
}

if (preg_match('/&false;\s+on\s+error/m', $returnValuesSection)) {
return true;
}
if (preg_match('/&false;\s+on\s+failure/m', $returnValuesSection)) {
return true;
}
if (preg_match('/&false;\s+otherwise/m', $returnValuesSection) && !preg_match('/(returns\s+&true;|&true;\s+on\s+success|&true;\s+if)/im', $returnValuesSection)) {
return true;
}
if (preg_match('/may\s+return\s+&false;/m', $returnValuesSection) && !preg_match('/(returns\s+&true;|&true;\s+on\s+success|&true;\s+if)/im', $returnValuesSection)) {
return true;
}
if (preg_match('/&false;\s+if\s+an\s+error\s+occurred/m', $returnValuesSection)) {
return true;
}
if (preg_match('/&return.success;/m', $returnValuesSection)) {
return true;
}
if (preg_match('/&return.nullorfalse;/m', $returnValuesSection)) {
return true;
}
if (preg_match('/&return.falseforfailure;/m', $returnValuesSection)) {
return true;
}
if (preg_match('/&date.datetime.return.modifiedobjectorfalseforfailure;/m', $returnValuesSection)) {
return true;
}
if (preg_match('/ or &false; \\(and generates an error/m', $returnValuesSection)) {
return true;
}
if (preg_match('/&false;\s+if\s+the\s+number\s+of\s+elements\s+for\s+each\s+array\s+isn\'t\s+equal/m', $returnValuesSection)) {
return true;
}
if (preg_match('/If\s+the\s+call\s+fails,\s+it\s+will\s+return\s+&false;/m', $returnValuesSection)) {
return true;
}
if (preg_match('/Upon\s+failure,?\s+\<function\>[\w_]{1,15}?\<\/function\>\s+returns\s+&false;/m', $returnValuesSection)) {
return true;
}
if (preg_match('/On\s+failure,\s+&false;\s+is\s+returned/m', $returnValuesSection)) {
return true;
}
if (preg_match('/on\s+success,\s+otherwise\s+&false;\s+is\s+returned/m', $returnValuesSection)) {
return true;
}
if (preg_match('/Returns.*on success[.\s\S]+Returns &false;\s+if/m', $returnValuesSection)) {
return true;
}

if (preg_match('/&gd\.return\.identifier;/m', $returnValuesSection)) {
return true;
}
//used for date
if (preg_match('/If a non-numeric value is used for
\<parameter\>timestamp\<\/parameter\>, &false; is returned/m', $returnValuesSection)) {
return true;
}

//used to detect imagecreatefromstring
if (preg_match('/&false; is returned if\s+the image type is unsupported, the data is not in a recognised format,\s+or the image is corrupt and cannot be loaded/m', $returnValuesSection)) {
return true;
}

//used to detect class_implements
if (preg_match("/&false; when the given class doesn't exist/m", $returnValuesSection)) {
return true;
}

//used to detect get_headers and ldap_search
if (preg_match("/&false; on failure/m", $returnValuesSection)) {
return true;
}

//used to detect inet_pton
if (preg_match("/&false; if a syntactically invalid/m", $returnValuesSection)) {
return true;
}

//used to detect shell_exec
if (preg_match("/&false; if the pipe\s+cannot be established/m", $returnValuesSection)) {
return true;
}

//used to detect cfg_get_var
if (preg_match("/&false; if an error occurs/m", $returnValuesSection)) {
return true;
}

// used to detect proc_open
if (preg_match("/On failure\s+returns &false;./m", $returnValuesSection)) {
return true;
}

return false;
$returnValuesSection = $this->getReturnValues();
$func = require FileCreator::getSafeRootDir() . '/generator/config/detectFalsyFunction.php';
return $func($returnValuesSection);
}

/*
* Detect function which return NULL on error.
*/
public function detectNullsyFunction(): bool
{
$file = \file_get_contents($this->path);
if ($file === false) {
throw new \RuntimeException('An error occurred while reading '.$this->path);
}

if ($this->getIsDeprecated($file)) {
return false;
}

// Only evaluate the text inside the `<refsect1 role="returnvalues">...</refsect1>` section of the doc page.
// This minimizes 'false positives', where text such as "returns false when ..." could be matched outside
// the function's dedicated Return Values section.
$returnValuesSection = $this->extractSection('returnvalues', $file);

if (preg_match('/&null;\s+on\s+failure/', $returnValuesSection)) {
return true;
}

// used to detect old (8.1) versions of array_replace
if (preg_match('/&null;\s+if\s+an\s+error\s+occurs/', $returnValuesSection)) {
// skip a false positive for shell-exec (the docs mention that it
// "returns null if an error occurs" _in the subprocess_ OR if the
// subprocess returns nothing, and users should use `exec` if they
// actually care about error handling)
if (str_ends_with($this->path, "shell-exec.xml")) {
return false;
}
return true;
}

return false;
$returnValuesSection = $this->getReturnValues();
$func = require FileCreator::getSafeRootDir() . '/generator/config/detectNullsyFunction.php';
return $func($returnValuesSection);
}

/*
* Detect function which return an empty string on error.
* Detect functions which return an empty string on error.
*/
public function detectEmptyFunction(): bool
{
$returnValuesSection = $this->getReturnValues();
$func = require FileCreator::getSafeRootDir() . '/generator/config/detectEmptyFunction.php';
return $func($returnValuesSection);
}

private function getReturnValues(): string
{
$file = file_get_contents($this->path);
if ($file === false) {
throw new \RuntimeException('An error occurred while reading '.$this->path);
}
if ($this->getIsDeprecated($file)) {
return false;
return "";
}

$returnValuesSection = $this->extractSection('returnvalues', $file);

if (preg_match('/an\s+empty\s+string\s+on\s+error/', $returnValuesSection)) {
return true;
}

return false;
// Only evaluate the text inside the `<refsect1 role="returnvalues">...</refsect1>` section of the doc page.
// This minimizes 'false positives', where text such as "returns false when ..." could be matched outside
// the function's dedicated Return Values section.
return $this->extractSection('returnvalues', $file);
}

/**
Expand Down

0 comments on commit 260c86c

Please sign in to comment.