以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/03/15/225829より取得しました。


SemgrepでJavaソースコードをASTベースで検索する

これは、なにをしたくて書いたもの?

以前、ast-grepを使ってJavaソースコードを検索してみました。

ASTベースのパターン検索ができるast-grepで、Javaのソースコードを検索する - CLOVER🍀

またSASTツールとしてSemgrepも使ってみました。

SASTツール、Semgrep Community Editionを試す - CLOVER🍀

SemgrepもルールはASTベースで書くので、ast-grepと同じようにASTベースでソースコードを検索できます。
ast-grepでやったことと同じことをSemgrepでもやってみようと思います。

Semgrepのパターン

Semgrepのパターン構文はこちらです。

Rule pattern syntax | Semgrep

いろいろ書いてあるのですが、チュートリアルをこなした方がわかりやすいかもですね。

Learn Semgrep Syntax

またこちらで言語ごとのパターンの例を見ることもできます。

Rule pattern syntax examples | Semgrep

Semgrepでのメタ変数は$で表現します。$CLASSなどで任意の定義にマッチできます。0個以上のシーケンスは...
マッチします。

このあたりは実際に使ってみた例を見た方が早いでしょうね。今回もJavaをお題にしてみます。

環境

今回の環境はこちら。

$ semgrep --version
1.155.0

Semgrepでソースコードを検索する

ast-grepの時と同じように、対象はRESTEasyのGitHubリポジトリーにします。

リポジトリーをclone。

$ git clone https://github.com/resteasy/resteasy
$ cd resteasy

検索は、semgrep scanで行います。scanは省略可です。

まずはクラス名で検索。

$ semgrep scan -l java -e 'ServerResponseWriter'
$ semgrep -l java -e 'ServerResponseWriter'

## または
$ semgrep scan --lang java --pattern 'ServerResponseWriter'
$ semgrep --lang java --pattern 'ServerResponseWriter'

-eまたは--patternでパターンを指定して検索する場合、-lまたは--langオプションによる言語指定は必須です。

$ semgrep -e 'ServerResponseWriter'
[00.06][ERROR]: -e/--pattern and -l/--lang must both be specified

結果はこちら。

$ semgrep -l java -e 'ServerResponseWriter'
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00                                                                                                                       


┌─────────────────┐
│ 9 Code Findings │
└─────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/AsyncResponseConsumer.java
          135┆ ServerResponseWriter.writeNomapResponse(builtResponse, httpRequest, httpResponse,
               dispatcher.getProviderFactory(),
            ⋮┆----------------------------------------
          309┆ ServerResponseWriter.setResponseMediaType(builtResponse, httpRequest, httpResponse,
               dispatcher.getProviderFactory(),
            ⋮┆----------------------------------------
          470┆ ServerResponseWriter.setResponseMediaType(builtResponse, httpRequest, httpResponse,
               dispatcher.getProviderFactory(),

    resteasy-core/src/main/java/org/jboss/resteasy/core/SynchronousDispatcher.java
          212┆ ServerResponseWriter.writeNomapResponse(((BuiltResponse) handledResponse), request,
               response, providerFactory,
            ⋮┆----------------------------------------
          474┆ ServerResponseWriter.writeNomapResponse((BuiltResponse) jaxrsResponse, request, response,
               providerFactory,
            ⋮┆----------------------------------------
          518┆ ServerResponseWriter.writeNomapResponse((BuiltResponse) jaxrsResponse, request, response,
               providerFactory,

    resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/SseEventOutputImpl.java
          144┆ ServerResponseWriter.writeNomapResponse(jaxrsResponse, request, response,
            ⋮┆----------------------------------------
          349┆ MediaType elementType = ServerResponseWriter.getResponseMediaType(jaxrsResponse, request,
               response,
            ⋮┆----------------------------------------
          425┆ ServerResponseWriter.writeNomapResponse(jaxrsResponse, request, response,



┌──────────────┐
│ Scan Summary │
└──────────────┘
✅ Scan completed successfully.
 • Findings: 9 (9 blocking)
 • Rules run: 1
 • Targets scanned: 914
 • Parsed lines: ~100.0%
 • Scan skipped:
   ◦ Files matching .semgrepignore patterns: 3154
 • Scan was limited to files tracked by git
 • For a detailed list of skipped files and lines, run semgrep with the --verbose flag
Ran 1 rule on 914 files: 9 findings.

クラス自体の定義はヒットしないみたいですね…。クラス定義にマッチさせる場合は、クラス定義全体に対してになります。

最後のサマリーや進捗の表示が不要な場合は、-qまたは--quietオプションを指定します。

$ semgrep -q -l java -e 'ServerResponseWriter'

以後、この形式で書いていきます。

レシーバーを指定してのメソッド呼び出し。...を使っているので、引数は任意の数でマッチします。

$ semgrep -q -l java -e '$VAR.setResponseMediaType(...)'


┌─────────────────┐
│ 2 Code Findings │
└─────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/AsyncResponseConsumer.java
          309┆ ServerResponseWriter.setResponseMediaType(builtResponse, httpRequest, httpResponse,
               dispatcher.getProviderFactory(),
          310┆         method);
            ⋮┆----------------------------------------
          470┆ ServerResponseWriter.setResponseMediaType(builtResponse, httpRequest, httpResponse,
               dispatcher.getProviderFactory(),
          471┆         method);

レシーバーの指定は任意にできなさそうです。

$ semgrep -q -l java -e 'setResponseMediaType(...)'


┌────────────────┐
│ 1 Code Finding │
└────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/ServerResponseWriter.java
           98┆ setResponseMediaType(jaxrsResponse, request, response, providerFactory, method);


$ semgrep -q -l java -e '....setResponseMediaType(...)'


┌─────────────────┐
│ 2 Code Findings │
└─────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/AsyncResponseConsumer.java
          309┆ ServerResponseWriter.setResponseMediaType(builtResponse, httpRequest, httpResponse,
               dispatcher.getProviderFactory(),
          310┆         method);
            ⋮┆----------------------------------------
          470┆ ServerResponseWriter.setResponseMediaType(builtResponse, httpRequest, httpResponse,
               dispatcher.getProviderFactory(),
          471┆         method);

メソッドの定義にマッチ。

$ semgrep -q -l java -e 'setResponseMediaType(...) { ... }'


┌────────────────┐
│ 1 Code Finding │
└────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/ServerResponseWriter.java
          190┆ public static void setResponseMediaType(BuiltResponse jaxrsResponse, HttpRequest request,
               HttpResponse response,
          191┆         ResteasyProviderFactory providerFactory, ResourceMethodInvoker method) {
          192┆     MediaType mt = getResponseMediaType(jaxrsResponse, request, response, providerFactory,
               method);
          193if (mt != null) {
          194┆         jaxrsResponse.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, mt);
          195}
          196}

logger変数でlogメソッドを呼び出している箇所にマッチ。

$ semgrep -q -l java -e 'logger.log(...)'


┌──────────────────┐
│ 10 Code Findings │
└──────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/SynchronousDispatcher.java
          298┆ logger.log("MATCH_RESOURCE", invoker);
            ⋮┆----------------------------------------
          299┆ logger.log("MATCH_RESOURCE_METHOD", invoker.getMethod());
            ⋮┆----------------------------------------
          386┆ logger.log("DISPATCH_RESPONSE", jaxrsResponse);

    resteasy-core/src/main/java/org/jboss/resteasy/core/registry/ClassNode.java
           55┆ logger.log("MATCH_RUNTIME_RESOURCE",
           56┆         expression,
           57┆         expression.getRegex(),
           58┆         expression.getRoot().root,
           59┆         expression.getPathExpression());

    resteasy-core/src/main/java/org/jboss/resteasy/core/registry/SegmentNode.java
           75┆ logger.log("MATCH_PATH_FIND", path);
            ⋮┆----------------------------------------
           88┆ logger.log("MATCH_PATH_SKIPPED", expression.getRegex());
            ⋮┆----------------------------------------
          128┆ logger.log("MATCH_LOCATOR", invoker.getMethod());
            ⋮┆----------------------------------------
          137┆ logger.log("MATCH_PATH_NOT_MATCHED", expression.getRegex());
            ⋮┆----------------------------------------
          148┆ logger.log("MATCH_PATH_SELECTED", match.match.expression.getRegex());

    resteasy-core/src/main/java/org/jboss/resteasy/tracing/RESTEasyTracingLoggerImpl.java
          137┆ logger.log(loggingLevel,
          138┆         new StringBuilder()
          139┆                 .append(requestId)
          140┆                 .append(' ')
          141┆                 .append(event.name())
          142┆                 .append(' ')
          143┆                 .append(message.toString())
          144┆                 .append(" [")
          145┆                 .append(tracingInfo.formatDuration(duration))
          146┆                 .append(" ms]")
             [hid 1 additional lines, adjust with --max-lines-per-finding]

指定の引数を使っているものにマッチ。

$ semgrep -q -l java -e 'logger.log("MATCH_RESOURCE", ...)'


┌────────────────┐
│ 1 Code Finding │
└────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/SynchronousDispatcher.java
          298┆ logger.log("MATCH_RESOURCE", invoker);

メソッド呼び出し結果の代入にマッチ。

$ semgrep -q -l java -e '$VAR = $OBJECT.getResourceInvoker(...)'


┌─────────────────┐
│ 3 Code Findings │
└─────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceLocatorInvoker.java
          162┆ ResourceInvoker invoker = registry.getResourceInvoker(request);

    resteasy-core/src/main/java/org/jboss/resteasy/core/SynchronousDispatcher.java
          293┆ ResourceInvoker invoker = registry.getResourceInvoker(request);

    resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/AbstractPatchMethodFilter.java
          160┆ resourceInovker = methodRegistry.getResourceInvoker(request);

ast-grepの結果と見比べると、テストコードが含まれていませんね…。

Semgrepも特定のクラスの中にあるメソッド定義にマッチさせることはできず、こう書くとすべてのserviceメソッドが
マッチします。なお、throwsを書かなくてもSemgrepの場合はマッチするみたいです…。

$ semgrep -q -l java -e 'service(...) { ... }'


┌─────────────────┐
│ 5 Code Findings │
└─────────────────┘

    resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/servlet/HttpServletDispatcher.java
           48┆ @Override
           49┆ protected void service(HttpServletRequest httpServletRequest, HttpServletResponse
               httpServletResponse)
           50┆         throws ServletException, IOException {
           51┆     service(httpServletRequest.getMethod(), httpServletRequest, httpServletResponse);
           52}
            ⋮┆----------------------------------------
           54┆ public void service(String httpMethod, HttpServletRequest request, HttpServletResponse
               response) throws IOException {
           55┆     servletContainerDispatcher.service(httpMethod, request, response, true);
           56}

    resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/servlet/ServletContainerDispatcher.java
          188┆ public void service(String httpMethod, HttpServletRequest request, HttpServletResponse
               response, boolean handleNotFound)
          189┆         throws IOException, NotFoundException {
          190┆     try {
          191┆         //logger.info(httpMethod + " " + request.getRequestURL().toString());
          192┆         //logger.info("***PATH: " + request.getRequestURL());
          193┆         // classloader/deployment aware RestasyProviderFactory.  Used to have request
               specific
          194┆         // ResteasyProviderFactory.getInstance()
          195┆         ResteasyProviderFactory defaultInstance = ResteasyProviderFactory.getInstance();
          196if (defaultInstance instanceof ThreadLocalResteasyProviderFactory) {
          197┆             ThreadLocalResteasyProviderFactory.push(providerFactory);
             [hid 39 additional lines, adjust with --max-lines-per-finding]

    resteasy-jsapi/src/main/java/org/jboss/resteasy/jsapi/JSAPIServlet.java
           55┆ @Override
           56┆ protected void service(HttpServletRequest req, HttpServletResponse resp)
           57┆         throws ServletException, IOException {
           58┆     String pathInfo = req.getPathInfo();
           59┆     String uri = req.getRequestURL().toString();
           60┆     uri = uri.substring(0, uri.length() - req.getServletPath().length());
           61if (LogMessages.LOGGER.isDebugEnabled()) {
           62┆         LogMessages.LOGGER.debug(Messages.MESSAGES.serving(pathInfo));
           63┆         LogMessages.LOGGER.debug(Messages.MESSAGES.query(req.getQueryString()));
           64}
             [hid 14 additional lines, adjust with --max-lines-per-finding]

    resteasy-wadl/src/main/java/org/jboss/resteasy/wadl/ResteasyWadlServlet.java
           51┆ @Override
           52┆ protected void service(HttpServletRequest req, HttpServletResponse resp)
           53┆         throws IOException {
           54┆     String pathInfo = req.getPathInfo();
           55┆     String uri = req.getRequestURL().toString();
           56┆     uri = uri.substring(0, uri.length() - req.getServletPath().length());
           57┆     LogMessages.LOGGER.debug(Messages.MESSAGES.servingPathInfo(pathInfo));
           58┆     LogMessages.LOGGER.debug(Messages.MESSAGES.query(req.getQueryString()));
           59if (this.services == null)
           60┆         scanResources();
             [hid 7 additional lines, adjust with --max-lines-per-finding]

こんなところでしょうか。

おわりに

SemgrepでJavaソースコードをASTベースで検索してみました。

ast-grepとの比較のつもりで軽く試してみたのですが、けっこう性質が違いますね。

それに雰囲気で使うと見落としが多発しそうな気がするので、本格的に使う場合はそれなりにパターンを確認して使い方を
学んでからでしょうね。

これはSemgrep、ast-grepのどちらにも言えると思います。

速度は圧倒的にast-grepの方が速いのですが、あとはパターンの書き方、マッチ度合いの好みなどで決めるのでしょうか…。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/03/15/225829より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14