Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sarif Output Format #5973

Closed
llaville opened this issue Nov 10, 2021 · 7 comments
Closed

Add Sarif Output Format #5973

llaville opened this issue Nov 10, 2021 · 7 comments

Comments

@llaville
Copy link

Feature request

SARIF (Static Analysis Results Interchange Format) is an OASIS Standard that defines an output file format. The SARIF standard is used to streamline how static analysis tools share their results by implementing a subset of the SARIF 2.1.0 JSON schema.

I'd like to see a new sarif output format in PHPStan.

Other Static Tool Analysers like

Because I don't found any PHP library that support SARIF v2 specifications, I've published a PHP SDK as a new bartlett/sarif-php-sdk package already available on packagist that implement the full v2.1.0 specifications.

There are many examples of all Sarif objects, in my github project page, that make it easy to include in any other PHP Static Tool Analyser.

Your feedbacks are welcome !

@ondrejmirtes
Copy link
Member

Hi, thank you for this suggestion, I'd appreciate if you'd contribute it yourself. Implementing a custom error formatter is really easy: https://phpstan.org/developing-extensions/error-formatters

@llaville
Copy link
Author

My implementation is over with basic unit tests (1 PASSED, 1 FAILED) following JsonErrorFormatter, and results LGTM.

I'll propose a PR tomorrow !

Here are some preview when analyze PHPStan code bin/ folder with command : bin/phpstan analyse bin/ --error-format=sarif

code
<?php declare(strict_types=1);

namespace PHPStan\Command\ErrorFormatter;

use Bartlett\Sarif\Definition\ArtifactLocation;
use Bartlett\Sarif\Definition\Location;
use Bartlett\Sarif\Definition\Message;
use Bartlett\Sarif\Definition\PhysicalLocation;
use Bartlett\Sarif\Definition\PropertyBag;
use Bartlett\Sarif\Definition\Region;
use Bartlett\Sarif\Definition\Result;
use Bartlett\Sarif\Definition\Run;
use Bartlett\Sarif\Definition\Tool;
use Bartlett\Sarif\Definition\ToolComponent;
use Bartlett\Sarif\SarifLog;

use PHPStan\Command\AnalysisResult;
use PHPStan\Command\Output;

/**
 * @author Laurent Laville
 */
class SarifErrorFormatter implements ErrorFormatter
{

	/**
	 * {@inheritDoc}
	 */
	public function formatErrors(AnalysisResult $analysisResult, Output $output): int
	{
		$driver = new ToolComponent('PHPStan');
		$driver->setInformationUri('https://phpstan.org');
		$driver->setVersion('1.1.2');

		$tool = new Tool($driver);

		$results = [];

		foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
			$file = $fileSpecificError->getFile();

			$result = new Result(new Message($fileSpecificError->getMessage()));

			$artifactLocation = new ArtifactLocation();
			$artifactLocation->setUri($this->pathToArtifactLocation($file));
			$artifactLocation->setUriBaseId('WORKINGDIR');

			$location = new Location();
			$physicalLocation = new PhysicalLocation($artifactLocation);
			$physicalLocation->setRegion(new Region($fileSpecificError->getLine()));
			$location->setPhysicalLocation($physicalLocation);
			$result->addLocations(array($location));

			$properties = new PropertyBag();
			$properties->addProperty('ignorable', $fileSpecificError->canBeIgnored());
			$result->setProperties($properties);

			$results[] = $result;
		}

		$run = new Run($tool);
		$workingDir = new ArtifactLocation();
		$workingDir->setUri($this->pathToUri(getcwd() . '/'));
		$originalUriBaseIds = array(
			'WORKINGDIR' => $workingDir
		);
		$run->addAdditionalProperties($originalUriBaseIds);
		$run->addResults($results);

		$log = new SarifLog([$run]);

		$output->writeLineFormatted((string) $log);

		return $analysisResult->hasErrors() ? 1 : 0;
	}

	/**
	 * Returns path to resource (file) scanned.
	 *
	 * @param string $path
	 * @return false|string
	 */
	protected function pathToArtifactLocation(string $path)
	{
		$workingDir = getcwd();
		if (substr($path, 0, strlen($workingDir)) === $workingDir) {
			// relative path
			return substr($path, strlen($workingDir) + 1);
		}

		// absolute path with protocol
		return $this->pathToUri($path);
	}

	/**
	 * Returns path to resource (file) scanned with protocol.
	 *
	 * @param string $path
	 * @return string
	 */
	protected function pathToUri(string $path): string
	{
		if (parse_url($path, PHP_URL_SCHEME) !== null) {
			// already a URL
			return $path;
		}

		$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);

		// file:///C:/... on Windows systems
		if (substr($path, 0, 1) !== '/') {
			$path = '/' . $path;
		}

		return 'file://' . $path;
	}

}
results
{
    "$schema": "https:\/\/json.schemastore.org\/sarif-2.1.0.json",
    "version": "2.1.0",
    "runs": [
        {
            "tool": {
                "driver": {
                    "name": "PHPStan",
                    "version": "1.1.2",
                    "informationUri": "https:\/\/phpstan.org"
                }
            },
            "originalUriBaseIds": {
                "WORKINGDIR": {
                    "uri": "file:\/\/\/shared\/backups\/github\/phpstan-src\/"
                }
            },
            "results": [
                {
                    "message": {
                        "text": "Method class@anonymous\/generate-changelog.php:12::configure() has no return type specified."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-changelog.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 14
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Access to an undefined property object::$number."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-changelog.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 61
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Access to an undefined property object::$number."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-changelog.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 61
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Access to an undefined property object::$user."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-changelog.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 62
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Class JetBrains\\PhpStorm\\Pure not found."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-function-metadata.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 30
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Class JetBrains\\PhpStorm\\Pure not found."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-function-metadata.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 46
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Calling PHPStan\\File\\FileReader::read() is not covered by backward compatibility promise. The method might change in a minor PHPStan version."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-function-metadata.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 66
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Parameter #1 $nodes of method PhpParser\\NodeTraverser::traverse() expects array<PhpParser\\Node>, array<PhpParser\\Node\\Stmt>|null given."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-function-metadata.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 66
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Calling PHPStan\\File\\FileWriter::write() is not covered by backward compatibility promise. The method might change in a minor PHPStan version."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/generate-function-metadata.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 116
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Parameter #1 $type of method PhpPatcher::printType() expects PhpParser\\Node\\Identifier|PhpParser\\Node\\Name|PhpParser\\Node\\NullableType|PhpParser\\Node\\UnionType, PhpParser\\Node\\ComplexType|PhpParser\\Node\\Identifier|PhpParser\\Node\\Name given."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 37
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Method PhpPatcher::printType() throws checked exception Exception but it's missing from the PHPDoc @throws tag."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 54
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Instanceof between PhpParser\\Node\\Identifier and PhpParser\\Node\\Identifier will always evaluate to true."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 66
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Unreachable statement - code above always terminates."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 70
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Class PhpPatcher does not have a constructor and must be instantiated without any parameters."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 100
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Only booleans are allowed in a negated boolean, int|false given."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 108
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Calling PHPStan\\File\\FileReader::read() is not covered by backward compatibility promise. The method might change in a minor PHPStan version."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 112
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Parameter #1 $nodes of method PhpParser\\NodeTraverser::traverse() expects array<PhpParser\\Node>, array<PhpParser\\Node\\Stmt>|null given."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 115
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Parameter #2 $origStmts of method PhpParser\\PrettyPrinterAbstract::printFormatPreserving() expects array<PhpParser\\Node>, array<PhpParser\\Node\\Stmt>|null given."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 116
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                },
                {
                    "message": {
                        "text": "Calling PHPStan\\File\\FileWriter::write() is not covered by backward compatibility promise. The method might change in a minor PHPStan version."
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "bin\/transform-source.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 120
                                }
                            }
                        }
                    ],
                    "properties": {
                        "ignorable": true
                    }
                }
            ]
        }
    ]
}

@llaville
Copy link
Author

PR phpstan/phpstan-src#765 available

@jbelien
Copy link

jbelien commented Jan 11, 2023

Any chance this could be re-open (since phpstan/phpstan-src#765 has been closed) ?

It would be really neat to have SARIF as output format so we can use PHPStan as Code Scanning Tool in GitHub (uploaded with https://github.com/github/codeql-action/blob/main/upload-sarif/action.yml)

@jbelien
Copy link

jbelien commented Jan 11, 2023

That being said, I just noticed there is a github output format (see documentation), I'll check if it's SARIF format.

EDIT: No it doesn't seem to be SARIF format.

@ondrejmirtes
Copy link
Member

There's now a package you can use: https://github.com/jbelien/phpstan-sarif-formatter

@github-actions
Copy link

github-actions bot commented Mar 2, 2023

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants