From 233d95b3f8ba6b1d5a72ea137128a53838fe715d Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Thu, 21 Dec 2023 22:17:11 +0000 Subject: [PATCH] php Signed-off-by: Prabhu Subramanian --- README.md | 6 ++-- build.sbt | 5 +-- codemeta.json | 4 +-- contrib/Dockerfile-graalvm21 | 6 ++-- src/main/scala/io/appthreat/atom/Atom.scala | 14 +++++++++ .../atom/slicing/ReachableSlicing.scala | 8 +++++ .../appthreat/atom/slicing/UsageSlicing.scala | 6 +++- wrapper/nodejs/astgen.js | 8 +++-- wrapper/nodejs/build.sh | 9 ++++++ wrapper/nodejs/index.js | 4 ++- wrapper/nodejs/package-lock.json | 7 +++-- wrapper/nodejs/package.json | 5 +-- wrapper/nodejs/phpastgen.js | 31 +++++++++++++++++++ wrapper/nodejs/utils.mjs | 10 ++++++ 14 files changed, 105 insertions(+), 18 deletions(-) create mode 100755 wrapper/nodejs/phpastgen.js diff --git a/README.md b/README.md index 2d5f2d2..0f9c184 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ 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 @@ -172,6 +172,7 @@ docker run --rm -v /tmp:/tmp -v $HOME:$HOME -v $(pwd):/app:rw -it ghcr.io/appthr - JavaScript - TypeScript - Python +- PHP ## Atom Specification @@ -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 diff --git a/build.sbt b/build.sbt index 9883b2b..9d8a50b 100644 --- a/build.sbt +++ b/build.sbt @@ -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 @@ -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", diff --git a/codemeta.json b/codemeta.json index 6830ada..37d1000 100644 --- a/codemeta.json +++ b/codemeta.json @@ -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", diff --git a/contrib/Dockerfile-graalvm21 b/contrib/Dockerfile-graalvm21 index 3fd7bb3..855cd13 100644 --- a/contrib/Dockerfile-graalvm21 +++ b/contrib/Dockerfile-graalvm21 @@ -5,14 +5,14 @@ LABEL maintainer="appthreat" \ org.opencontainers.image.authors="Team AppThreat " \ 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}" \ @@ -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 \ diff --git a/src/main/scala/io/appthreat/atom/Atom.scala b/src/main/scala/io/appthreat/atom/Atom.scala index 789e2f8..00ca631 100644 --- a/src/main/scala/io/appthreat/atom/Atom.scala +++ b/src/main/scala/io/appthreat/atom/Atom.scala @@ -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, @@ -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'" diff --git a/src/main/scala/io/appthreat/atom/slicing/ReachableSlicing.scala b/src/main/scala/io/appthreat/atom/slicing/ReachableSlicing.scala index 8ae2869..9eb2feb 100644 --- a/src/main/scala/io/appthreat/atom/slicing/ReachableSlicing.scala +++ b/src/main/scala/io/appthreat/atom/slicing/ReachableSlicing.scala @@ -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( diff --git a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala index 4faa6a4..93b066a 100644 --- a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala +++ b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala @@ -45,7 +45,8 @@ 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 @@ -53,6 +54,9 @@ object UsageSlicing: 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 diff --git a/wrapper/nodejs/astgen.js b/wrapper/nodejs/astgen.js index d20d06d..302a957 100755 --- a/wrapper/nodejs/astgen.js +++ b/wrapper/nodejs/astgen.js @@ -98,6 +98,7 @@ const vueTemplateRegex = /()([\s\S]*)(<\/template>)/gi; const vueCommentRegex = //gi; const vueBindRegex = /(:\[)([\s\S]*?)(\])/gi; const vuePropRegex = /\s([.:@])([a-zA-Z]*?=)/gi; +const vueOpenImgTag = /()[\s\S]+?)( [^/]>)/gi; /** * Convert a single vue file to AST @@ -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; @@ -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); diff --git a/wrapper/nodejs/build.sh b/wrapper/nodejs/build.sh index eb1bb92..8d8ba57 100755 --- a/wrapper/nodejs/build.sh +++ b/wrapper/nodejs/build.sh @@ -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 diff --git a/wrapper/nodejs/index.js b/wrapper/nodejs/index.js index c265717..0212dcc 100755 --- a/wrapper/nodejs/index.js +++ b/wrapper/nodejs/index.js @@ -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 = `${ @@ -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, { diff --git a/wrapper/nodejs/package-lock.json b/wrapper/nodejs/package-lock.json index 1ddf965..19ac205 100644 --- a/wrapper/nodejs/package-lock.json +++ b/wrapper/nodejs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@appthreat/atom", - "version": "1.8.0", + "version": "1.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@appthreat/atom", - "version": "1.8.0", + "version": "1.8.1", "license": "Apache-2.0", "dependencies": { "@babel/parser": "^7.23.6", @@ -15,7 +15,8 @@ }, "bin": { "astgen": "astgen.js", - "atom": "index.js" + "atom": "index.js", + "phpastgen": "phpastgen.js" }, "devDependencies": { "eslint": "^8.56.0" diff --git a/wrapper/nodejs/package.json b/wrapper/nodejs/package.json index 339e131..7468c01 100644 --- a/wrapper/nodejs/package.json +++ b/wrapper/nodejs/package.json @@ -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", @@ -18,7 +18,8 @@ }, "bin": { "atom": "./index.js", - "astgen": "./astgen.js" + "astgen": "./astgen.js", + "phpastgen": "./phpastgen.js" }, "engines": { "node": ">=16.0.0" diff --git a/wrapper/nodejs/phpastgen.js b/wrapper/nodejs/phpastgen.js new file mode 100755 index 0000000..49f3796 --- /dev/null +++ b/wrapper/nodejs/phpastgen.js @@ -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)); diff --git a/wrapper/nodejs/utils.mjs b/wrapper/nodejs/utils.mjs index c49de50..0f659d0 100644 --- a/wrapper/nodejs/utils.mjs +++ b/wrapper/nodejs/utils.mjs @@ -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; +};