All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.codemodder.SarifPluginJavaParserChanger Maven / Gradle / Ivy

package io.codemodder;

import com.contrastsecurity.sarif.Region;
import com.contrastsecurity.sarif.Result;
import com.github.javaparser.Range;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.google.common.annotations.VisibleForTesting;
import io.codemodder.javaparser.JavaParserChanger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Provides base functionality for making JavaParser-based changes based on results found by a sarif
 * file.
 */
public abstract class SarifPluginJavaParserChanger extends JavaParserChanger {

  @VisibleForTesting public final RuleSarif sarif;
  private final Class nodeType;
  private final RegionExtractor regionExtractor;
  private final RegionNodeMatcher regionNodeMatcher;

  protected SarifPluginJavaParserChanger(
      final RuleSarif sarif, final Class nodeType) {
    this(sarif, nodeType, RegionExtractor.FROM_FIRST_LOCATION, RegionNodeMatcher.EXACT_MATCH);
  }

  protected SarifPluginJavaParserChanger(
      final RuleSarif sarif,
      final Class nodeType,
      final RegionExtractor regionExtractor) {
    this(sarif, nodeType, regionExtractor, RegionNodeMatcher.EXACT_MATCH);
  }

  protected SarifPluginJavaParserChanger(
      final RuleSarif sarif,
      final Class nodeType,
      final CodemodReporterStrategy codemodReporterStrategy) {
    this(sarif, nodeType, RegionNodeMatcher.EXACT_MATCH, codemodReporterStrategy);
  }

  protected SarifPluginJavaParserChanger(
      final RuleSarif sarif,
      final Class nodeType,
      final RegionNodeMatcher regionNodeMatcher) {
    this(sarif, nodeType, RegionExtractor.FROM_FIRST_LOCATION, regionNodeMatcher);
  }

  protected SarifPluginJavaParserChanger(
      final RuleSarif sarif,
      final Class nodeType,
      final RegionNodeMatcher regionNodeMatcher,
      final CodemodReporterStrategy reporterStrategy) {
    this(sarif, nodeType, RegionExtractor.FROM_FIRST_LOCATION, regionNodeMatcher, reporterStrategy);
  }

  protected SarifPluginJavaParserChanger(
      final RuleSarif sarif,
      final Class nodeType,
      final RegionExtractor regionExtractor,
      final RegionNodeMatcher regionNodeMatcher) {
    this.sarif = Objects.requireNonNull(sarif);
    this.nodeType = Objects.requireNonNull(nodeType);
    this.regionExtractor = Objects.requireNonNull(regionExtractor);
    this.regionNodeMatcher = Objects.requireNonNull(regionNodeMatcher);
  }

  protected SarifPluginJavaParserChanger(
      final RuleSarif sarif,
      final Class nodeType,
      final RegionExtractor regionExtractor,
      final RegionNodeMatcher regionNodeMatcher,
      final CodemodReporterStrategy reporter) {
    super(reporter);
    this.sarif = Objects.requireNonNull(sarif);
    this.nodeType = Objects.requireNonNull(nodeType);
    this.regionExtractor = Objects.requireNonNull(regionExtractor);
    this.regionNodeMatcher = Objects.requireNonNull(regionNodeMatcher);
  }

  public List visit(
      final CodemodInvocationContext context, final CompilationUnit cu) {
    List results = sarif.getResultsByPath(context.path());

    // small shortcut to avoid always executing the expensive findAll
    if (results.isEmpty()) {
      return List.of();
    }

    List allNodes = cu.findAll(nodeType);

    /*
     * We have an interesting scenario we have to handle whereby we could accidentally feed two results that should be
     * applied only once. Consider this real-world example where a SARIF says we have 1 problem on line 101, column 3:
     *
     * 100. public void foo() {
     * 101.   Runtime.getRuntime().exec(...);
     * 102. }
     *
     * There are actually 2 different JavaParser nodes that match the position reported in SARIF:
     * - getRuntime()
     * - exec(...)
     *
     * If we apply the change to both nodes, we'll end up with a broken AST. So we need to keep track of the nodes we've
     * already applied changes to, and skip them if we encounter them again. Deciding which of the nodes we want to act
     * on is unfortunately a job for the subclass -- they should just "return false" if the event didn't make sense, but
     * we should invest into a general solution if this doesn't scale.
     */
    List codemodChanges = new ArrayList<>();
    for (Result result : results) {
      for (Node node : allNodes) {
        Region region = regionExtractor.from(result);
        if (!nodeType.isAssignableFrom(node.getClass())) {
          continue;
        }
        if (context.lineIncludesExcludes().matches(region.getStartLine())) {
          if (node.getRange().isPresent()) {
            Range range = node.getRange().get();
            if (regionNodeMatcher.matches(region, range)) {
              boolean changeSuccessful = onResultFound(context, cu, (T) node, result);
              if (changeSuccessful) {
                codemodChanges.add(
                    CodemodChange.from(region.getStartLine(), dependenciesRequired()));
              }
            }
          }
        }
      }
    }
    return codemodChanges;
  }

  /**
   * Creates a visitor for the given context and locations.
   *
   * @param context the context of this files transformation
   * @param cu the parsed model of the file being transformed
   * @param node the node to act on
   * @param result the given SARIF result to act on
   * @return true, if the change was made, false otherwise
   */
  public abstract boolean onResultFound(
      CodemodInvocationContext context, CompilationUnit cu, T node, Result result);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy