Skip to content

Commit

Permalink
php (#107)
Browse files Browse the repository at this point in the history
Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>
  • Loading branch information
prabhu authored Jan 8, 2024
1 parent 1102f51 commit 1af339d
Show file tree
Hide file tree
Showing 14 changed files with 105 additions and 18 deletions.
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 <cloud@appthreat.com>" \
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;
};

0 comments on commit 1af339d

Please sign in to comment.