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

php support #107

Merged
merged 1 commit into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,15 @@ docker run --rm -v /tmp:/tmp -v $HOME:$HOME -v $(pwd):/app:rw -it ghcr.io/appthr

## Languages supported

- C/C++ (Requires Java 17 or above)
- C/C++
- H (C/C++ Header files alone)
- Java (Requires compilation)
- Jar
- Android APK (Requires Android SDK. Set the environment variable `ANDROID_HOME`)
- JavaScript
- TypeScript
- Python
- PHP

## Atom Specification

Expand Down Expand Up @@ -210,7 +211,8 @@ Apache-2.0

## Developing / Contributing

Install Java 17 or 21 (Recommended)
Install Java 21
Node.js > 21

```shell
sbt clean stage scalafmt test createDistribution
Expand Down
5 changes: 3 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name := "atom"
ThisBuild / organization := "io.appthreat"
ThisBuild / version := "1.8.0"
ThisBuild / version := "1.8.1"
ThisBuild / scalaVersion := "3.3.1"

val chenVersion = "1.1.1"
val chenVersion = "1.1.3"

lazy val atom = Projects.atom

Expand All @@ -24,6 +24,7 @@ libraryDependencies ++= Seq(
"io.appthreat" %% "javasrc2cpg" % Versions.chen,
"io.appthreat" %% "jssrc2cpg" % Versions.chen,
"io.appthreat" %% "jimple2cpg" % Versions.chen,
"io.appthreat" %% "php2atom" % Versions.chen,
"io.appthreat" %% "semanticcpg" % Versions.chen % Test classifier "tests",
"io.appthreat" %% "x2cpg" % Versions.chen % Test classifier "tests",
"io.appthreat" %% "pysrc2cpg" % Versions.chen % Test classifier "tests",
Expand Down
4 changes: 2 additions & 2 deletions codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"downloadUrl": "https://github.com/AppThreat/atom",
"issueTracker": "https://github.com/AppThreat/atom/issues",
"name": "atom",
"version": "1.8.0",
"description": ".",
"version": "1.8.1",
"description": "Atom is a novel intermediate representation for next-generation code analysis.",
"applicationCategory": "code-analysis",
"keywords": [
"static-analysis",
Expand Down
6 changes: 3 additions & 3 deletions contrib/Dockerfile-graalvm21
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ LABEL maintainer="appthreat" \
org.opencontainers.image.authors="Team AppThreat <[email protected]>" \
org.opencontainers.image.source="https://github.com/appthreat/chen" \
org.opencontainers.image.url="https://github.com/appthreat/atom" \
org.opencontainers.image.version="1.7.x" \
org.opencontainers.image.version="1.8.x" \
org.opencontainers.image.vendor="appthreat" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.title="atom" \
org.opencontainers.image.description="Container image for testing AppThreat atom with Oracle GraalVM" \
org.opencontainers.docker.cmd="docker run --rm -v /tmp:/tmp -v $HOME:$HOME -v $(pwd):/app:rw -it ghcr.io/appthreat/atom"

ARG MAVEN_VERSION=3.9.5
ARG MAVEN_VERSION=3.9.6

ENV MAVEN_VERSION=$MAVEN_VERSION \
MAVEN_HOME="/opt/maven/${MAVEN_VERSION}" \
Expand All @@ -36,7 +36,7 @@ RUN set -e; \
;; \
*) echo >&2 "error: unsupported architecture: '$ARCH_NAME'"; exit 1 ;; \
esac; \
echo -e "[nodejs]\nname=nodejs\nstream=21\nprofiles=\nstate=enabled\n" > /etc/dnf/modules.d/nodejs.module \
echo -e "[nodejs]\nname=nodejs\nstream=20\nprofiles=\nstate=enabled\n" > /etc/dnf/modules.d/nodejs.module \
&& microdnf install -y gcc git-core wget bash glibc-common glibc-all-langpacks \
pcre2 findutils which tar gzip zip unzip sudo nodejs \
&& curl -s "https://get.sdkman.io" | bash \
Expand Down
14 changes: 14 additions & 0 deletions src/main/scala/io/appthreat/atom/Atom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import io.appthreat.jssrc2cpg.passes.{
JavaScriptInheritanceNamePass
}
import io.appthreat.jssrc2cpg.{JsSrc2Cpg, Config as JSConfig}
import io.appthreat.php2atom.passes.PhpSetKnownTypesPass
import io.appthreat.php2atom.{Php2Atom, Config as PhpConfig}
import io.appthreat.pysrc2cpg.{
DynamicTypeHintFullNamePass,
Py2CpgOnFileSystem,
Expand Down Expand Up @@ -514,6 +516,18 @@ object Atom:
new AstLinkerPass(ag).createAndApply()
ag
}
case Languages.PHP =>
new Php2Atom().createCpgWithOverlays(
PhpConfig()
.withDisableDummyTypes(true)
.withInputPath(config.inputPath.pathAsString)
.withOutputPath(outputAtomFile)
.withDefaultIgnoredFilesRegex(List("\\..*".r))
.withIgnoredFilesRegex(".*(samples|examples|docs|tests).*")
).map { ag =>
new PhpSetKnownTypesPass(ag).createAndApply()
ag
}
case _ => Failure(
new RuntimeException(
s"No language frontend supported for language '$language'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ object ReachableSlicing:
.map(toSlice)
.toList
end if
if language == Languages.PHP
then
flowsList ++= atom.ret.where(_.tag.name(config.sinkTag)).reachableByFlows(
atom.tag.name(config.sourceTag).parameter
).map(toSlice).toList
flowsList ++= atom.tag.name(FRAMEWORK_TAG).parameter.reachableByFlows(
atom.tag.name(config.sourceTag).parameter
).map(toSlice).toList
if language == Languages.NEWC || language == Languages.C
then
flowsList ++= atom.tag.name(LIBRARY_CALL_TAG).call.reachableByFlows(atom.tag.name(
Expand Down
6 changes: 5 additions & 1 deletion src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,18 @@ object UsageSlicing:
val slices = usageSlices(atom, () => getDeclarations, typeMap)
val language = atom.metaData.language.headOption
val userDefTypes = userDefinedTypes(atom)
if language.get == Languages.NEWC || language.get == Languages.C then
if language.get == Languages.NEWC || language.get == Languages.C
then
ProgramUsageSlice(slices ++ importsAsSlices(atom), userDefTypes ++ routesAsUDT(atom))
else if language.get == Languages.PYTHON || language.get == Languages.PYTHONSRC
then
ProgramUsageSlice(
slices ++ externalCalleesAsSlices(atom, typeMap),
userDefTypes ++ routesAsUDT(atom)
)
else if language.get == Languages.PHP
then
ProgramUsageSlice(slices, userDefTypes ++ routesAsUDT(atom))
else
ProgramUsageSlice(slices ++ unusedTypeDeclAsSlices(atom), userDefTypes)
end calculateUsageSlice
Expand Down
8 changes: 6 additions & 2 deletions wrapper/nodejs/astgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const vueTemplateRegex = /(<template.*>)([\s\S]*)(<\/template>)/gi;
const vueCommentRegex = /<!--[\s\S]*?-->/gi;
const vueBindRegex = /(:\[)([\s\S]*?)(\])/gi;
const vuePropRegex = /\s([.:@])([a-zA-Z]*?=)/gi;
const vueOpenImgTag = /(<img)((?!>)[\s\S]+?)( [^/]>)/gi;

/**
* Convert a single vue file to AST
Expand All @@ -115,7 +116,10 @@ const toVueAst = (file) => {
return grA.replaceAll(/\S/g, " ") + grB + grC.replaceAll(/\S/g, " ");
})
.replace(vuePropRegex, function (match, grA, grB) {
return " " + grA.replace(/[.:@]/g, " ") + grB;
return " " + grA.replace(/[.:@]/g, " ") + grB.replaceAll(".", "-");
})
.replace(vueOpenImgTag, function (match, grA, grB, grC) {
return grA + grB + grC.replace(" >", "/>");
})
.replace(vueTemplateRegex, function (match, grA, grB, grC) {
return grA + grB.replaceAll("{{", "{ ").replaceAll("}}", " }") + grC;
Expand Down Expand Up @@ -256,7 +260,7 @@ const createJSAst = async (options) => {
* Generate AST for .vue files
*/
const createVueAst = async (options) => {
const srcFiles = getAllFiles(options.src, ".vue");
const srcFiles = await getAllFiles(options.src, ".vue");
for (const file of srcFiles) {
try {
const ast = toVueAst(file);
Expand Down
9 changes: 9 additions & 0 deletions wrapper/nodejs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,13 @@ if [ -e "../../target/atom.zip" ]; then
else
echo "Build the atom project using 'sbt createDistribution' before running this script"
fi
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"
export COMPOSER_VENDOR_DIR=plugins
php composer.phar require nikic/php-parser --ignore-platform-reqs --optimize-autoloader

npm install

rm composer.phar composer.json composer.lock
4 changes: 3 additions & 1 deletion wrapper/nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const _version = selfPJson.version;
export const LOG4J_CONFIG = join(dirName, "plugins", "log4j2.xml");
export const ATOM_HOME = join(dirName, "plugins");
export const APP_LIB_DIR = join(ATOM_HOME, "lib");
export const PHP_PARSER_BIN = join(ATOM_HOME, "bin", "php-parse");
const freeMemoryGB = Math.max(Math.floor(freemem() / 1024 / 1024 / 1024), 4);
export const JVM_ARGS = "-XX:+UseG1GC -XX:+UseStringDeduplication";
export const JAVA_OPTS = `${
Expand Down Expand Up @@ -66,7 +67,8 @@ export const executeAtom = (atomArgs) => {
]);
const env = {
...process.env,
ATOM_HOME
ATOM_HOME,
PHP_PARSER_BIN
};
const cwd = process.env.ATOM_CWD || process.cwd();
spawnSync(JAVACMD, args, {
Expand Down
7 changes: 4 additions & 3 deletions wrapper/nodejs/package-lock.json

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

5 changes: 3 additions & 2 deletions wrapper/nodejs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@appthreat/atom",
"version": "1.8.0",
"version": "1.8.1",
"description": "Create atom (⚛) representation for your application, packages and libraries",
"exports": "./index.js",
"type": "module",
Expand All @@ -18,7 +18,8 @@
},
"bin": {
"atom": "./index.js",
"astgen": "./astgen.js"
"astgen": "./astgen.js",
"phpastgen": "./phpastgen.js"
},
"engines": {
"node": ">=16.0.0"
Expand Down
31 changes: 31 additions & 0 deletions wrapper/nodejs/phpastgen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node

import { dirname, join } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { detectPhp } from "./utils.mjs";

let url = import.meta.url;
if (!url.startsWith("file://")) {
url = new URL(`file://${import.meta.url}`).toString();
}
const dirName = import.meta ? dirname(fileURLToPath(url)) : __dirname;
export const PLUGINS_HOME = join(dirName, "plugins");
const PHP_PARSER_BIN =
process.env.PHP_PARSER_BIN || join(PLUGINS_HOME, "bin", "php-parse");

function main(argvs) {
if (!detectPhp()) {
console.warn("PHP is not installed!");
return false;
}
const cwd = process.env.ATOM_CWD || process.cwd();
spawnSync(PHP_PARSER_BIN, argvs, {
encoding: "utf-8",
cwd,
stdio: "inherit",
stderr: "inherit",
timeout: process.env.ATOM_TIMEOUT || process.env.ASTGEN_TIMEOUT
});
}
main(process.argv.slice(2));
10 changes: 10 additions & 0 deletions wrapper/nodejs/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,13 @@ export const detectJava = () => {
}
return true;
};

export const detectPhp = () => {
let result = spawnSync(process.env.JAVA_CMD || "php", ["--version"], {
encoding: "utf-8"
});
if (result.status !== 0 || result.error) {
return false;
}
return true;
};