Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,17 @@ private void visitClass(ClassTree tree) {
}

private void visitMethodInvocation(MethodInvocationTree tree) {
if (isInvocation(tree, "index_jsp.java", 116) ||
isInvocation(tree, "test_005finclude_jsp.java", 124) ||
isInvocation(tree, "test_005finclude_jsp.java", 129)) {
if (isInvocation(tree, "index_jsp.java", "write") ||
isInvocation(tree, "test_005finclude_jsp.java", "getParameter") ||
isInvocation(tree, "test_005finclude_jsp.java", "print")) {
context.sourceMap()
.flatMap(sourceMap -> sourceMap.sourceMapLocationFor(tree))
.ifPresent(this::writeToFile);
}
}

private boolean isInvocation(MethodInvocationTree tree, String inputFile, int line) {
return context.getInputFile().filename().equals(inputFile) && tree.firstToken().range().start().line() == line;
private boolean isInvocation(MethodInvocationTree tree, String inputFile, String methodName) {
return context.getInputFile().filename().equals(inputFile) && tree.methodSymbol().name().equals(methodName);
}

private void writeToFile(SourceMap.Location location) {
Expand Down
21 changes: 14 additions & 7 deletions its/plugin/projects/servlet-jsp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<jakarta.jstl.version>3.0.1</jakarta.jstl.version>
<owasp.encoder.version>1.4.0</owasp.encoder.version>

<sonar.sources>src/main/java,src/main/webapp</sonar.sources>
</properties>
Expand All @@ -24,21 +26,26 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>${jakarta.jstl.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>${jakarta.jstl.version}</version>
</dependency>
<!-- OWASP Sanitization Encoder (Java) -->
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder</artifactId>
<version>1.2.2</version>
<version>${owasp.encoder.version}</version>
</dependency>
<!-- OWASP Sanitization Encoder (JSP) -->
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder-jsp</artifactId>
<version>1.2.2</version>
<artifactId>encoder-jakarta-jsp</artifactId>
<version>${owasp.encoder.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %>
<%@ taglib prefix="c" uri="jakarta.tags.core"%>
<%@ taglib prefix="e" uri="owasp.encoder.jakarta" %>

<%@ page import="org.owasp.encoder.Encode" %>

Expand Down
4 changes: 3 additions & 1 deletion java-frontend/src/main/java/org/sonar/java/JavaFrontend.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public class JavaFrontend {
private final SonarComponents sonarComponents;
private final Telemetry telemetry;
private final List<File> globalClasspath;
private final List<File> autoScanClasspath;
private final JavaAstScanner astScanner;
private final JavaAstScanner astScannerForTests;
private final JavaAstScanner astScannerForGeneratedFiles;
Expand Down Expand Up @@ -108,6 +109,7 @@ public JavaFrontend(JavaVersion javaVersion, SonarComponents sonarComponents, Me

globalClasspath = Stream.of(classpath, testClasspath, jspClasspath)
.flatMap(Collection::stream).distinct().toList();
autoScanClasspath = sonarComponents.isAutoScan() ? sonarComponents.getAutoScanClasspath() : List.of();

//AstScanner for main files
astScanner = new JavaAstScanner(sonarComponents, telemetry, TelemetryKey.JAVA_ANALYSIS_MAIN);
Expand Down Expand Up @@ -279,7 +281,7 @@ public String descriptor(InputFile input) {

@Override
public List<File> getClasspath() {
return globalClasspath;
return autoScanClasspath;
}

@Override
Expand Down
72 changes: 72 additions & 0 deletions java-frontend/src/main/java/org/sonar/java/SonarComponents.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -34,6 +38,9 @@
import java.util.TreeSet;
import java.util.function.LongSupplier;
import java.util.function.UnaryOperator;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
Expand Down Expand Up @@ -83,6 +90,7 @@ public class SonarComponents extends CheckRegistrar.RegistrarContext {

private static final Logger LOG = LoggerFactory.getLogger(SonarComponents.class);
private static final int LOGGED_MAX_NUMBER_UNDEFINED_TYPES = 50;
private static final List<String> JSP_RUNTIME_API_PREFIXES = List.of("jakarta/el/", "jakarta/servlet/");

public static final String FAIL_ON_EXCEPTION_KEY = "sonar.internal.analysis.failFast";
public static final String SONAR_BATCH_MODE_KEY = "sonar.java.internal.batchMode";
Expand Down Expand Up @@ -137,6 +145,9 @@ public class SonarComponents extends CheckRegistrar.RegistrarContext {
private UnaryOperator<List<JavaCheck>> checkFilter = UnaryOperator.identity();
private final Set<RuleKey> additionalAutoScanCompatibleRuleKeys;

@Nullable
private File autoScanPluginJar;

private boolean alreadyLoggedSkipStatus = false;

public SonarComponents(FileLinesContextFactory fileLinesContextFactory, FileSystem fs,
Expand Down Expand Up @@ -275,6 +286,67 @@ public List<File> getJspClasspath() {
return jspClasspath;
}

public List<File> getAutoScanClasspath() {
return getAutoScanClasspath(findPluginJar());
}

@VisibleForTesting
List<File> getAutoScanClasspath(File pluginJar) {
List<File> autoScanClasspath = new ArrayList<>();
// Jasper is disabled in AutoScan; do not expose its bundled Jakarta APIs as inferred project dependencies.
autoScanClasspath.add(pluginJarWithoutJspRuntimeApis(pluginJar));
autoScanClasspath.addAll(getJavaClasspath());
autoScanClasspath.addAll(getJavaTestClasspath());
return autoScanClasspath.stream().distinct().toList();
}

private File pluginJarWithoutJspRuntimeApis(File pluginJar) {
if (autoScanPluginJar != null) {
return autoScanPluginJar;
}
if (!pluginJar.isFile()) {
// Unit tests and IDE executions load analyzer classes from a directory rather than a shaded plugin JAR.
return pluginJar;
}
Path outputDirectory = fs.workDir().toPath().resolve("autoscan");
Path target = outputDirectory.resolve(pluginJar.getName());
try {
Files.createDirectories(outputDirectory);
Path temporary = Files.createTempFile(outputDirectory, "sonar-java-plugin-", ".jar");
try {
createAutoScanPluginJar(pluginJar.toPath(), temporary);
Files.move(temporary, target, StandardCopyOption.REPLACE_EXISTING);
} finally {
Files.deleteIfExists(temporary);
}
autoScanPluginJar = target.toFile();
return autoScanPluginJar;
} catch (IOException e) {
throw new AnalysisException("Failed to prepare the Java analyzer classpath for AutoScan.", e);
}
}

@VisibleForTesting
static void createAutoScanPluginJar(Path source, Path target) throws IOException {
try (JarFile sourceJar = new JarFile(source.toFile());
JarOutputStream targetJar = new JarOutputStream(Files.newOutputStream(target))) {
Enumeration<JarEntry> entries = sourceJar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (JSP_RUNTIME_API_PREFIXES.stream().anyMatch(entry.getName()::startsWith)) {
continue;
}
targetJar.putNextEntry(new JarEntry(entry));
if (!entry.isDirectory()) {
try (var input = sourceJar.getInputStream(entry)) {
input.transferTo(targetJar);
}
}
targetJar.closeEntry();
}
}
}

/**
* @return the jar of sonar-java plugin
*/
Expand Down
19 changes: 19 additions & 0 deletions java-frontend/src/test/java/org/sonar/java/JavaFrontendTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,25 @@ void test_as_autoscan_main_and_test() throws IOException {
assertThat(testCodeIssueScannerAndFilter.scanFileInvocationCount).isEqualTo(1);
}

@Test
void autoscan_should_use_its_dedicated_classpath() {
SonarComponents components = mockSonarComponents();
List<File> autoScanClasspath = List.of(new File("autoscan-plugin.jar"));
when(components.isAutoScan()).thenReturn(true);
when(components.getAutoScanClasspath()).thenReturn(autoScanClasspath);

JavaFrontend frontend = new JavaFrontend(
new JavaVersionImpl(),
components,
mock(Measurer.class),
new NoOpTelemetry(),
mock(JavaResourceLocator.class),
mainCodeIssueScannerAndFilter
);

assertThat(frontend.new AutoScanBatchContext().getClasspath()).isEqualTo(autoScanClasspath);
}

@Test
void test_end_of_analysis_should_be_called_once() throws IOException {
scan(SONARLINT_RUNTIME, "class A {}", "class B {}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.text.DecimalFormat;
Expand All @@ -32,6 +33,9 @@
import java.util.List;
import java.util.Optional;
import java.util.function.LongSupplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;

Expand All @@ -40,6 +44,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
Expand Down Expand Up @@ -727,6 +732,71 @@ void jsp_classpath_should_include_plugin() {
assertThat(jspClassPath).containsExactly(plugin.getAbsolutePath(), someJar.getAbsolutePath());
}

@Test
void autoscan_classpath_should_filter_plugin_jar_and_cache_it(@TempDir Path tempDirectory) throws IOException {
Path pluginJar = createPluginJar(tempDirectory.resolve("plugin.jar"));
File mainClasspath = new File("main.jar");
File testClasspath = new File("test.jar");
ClasspathForMain javaClasspath = mock(ClasspathForMain.class);
ClasspathForTest javaTestClasspath = mock(ClasspathForTest.class);
when(javaClasspath.getElements()).thenReturn(List.of(mainClasspath));
when(javaTestClasspath.getElements()).thenReturn(List.of(testClasspath));

SensorContextTester sensorContextTester = SensorContextTester.create(tempDirectory);
DefaultFileSystem fs = sensorContextTester.fileSystem();
fs.setWorkDir(tempDirectory.resolve("work"));
SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, fs, javaClasspath, javaTestClasspath,
checkFactory, context.activeRules());
List<File> autoScanClasspath = sonarComponents.getAutoScanClasspath(pluginJar.toFile());
File autoScanPlugin = autoScanClasspath.get(0);

assertThat(autoScanClasspath).containsExactly(autoScanPlugin, mainClasspath, testClasspath);
assertThat(autoScanPlugin.toPath()).isEqualTo(tempDirectory.resolve("work/autoscan/plugin.jar"));
assertThat(sonarComponents.getAutoScanClasspath(pluginJar.toFile()).get(0)).isSameAs(autoScanPlugin);
try (JarFile jar = new JarFile(autoScanPlugin)) {
assertThat(Collections.list(jar.entries()))
.extracting(JarEntry::getName)
.containsExactly("org/sonar/java/Analyzer.class", "javax/servlet/Servlet.class");
assertThat(new String(jar.getInputStream(jar.getJarEntry("javax/servlet/Servlet.class")).readAllBytes(), StandardCharsets.UTF_8))
.isEqualTo("javax/servlet/Servlet.class");
}
}

@Test
void autoscan_classpath_should_report_plugin_copy_failures(@TempDir Path tempDirectory) throws IOException {
Path pluginJar = createPluginJar(tempDirectory.resolve("plugin.jar"));
File pluginFile = pluginJar.toFile();
Path invalidWorkDirectory = tempDirectory.resolve("not-a-directory");
Files.writeString(invalidWorkDirectory, "file");
SensorContextTester sensorContextTester = SensorContextTester.create(tempDirectory);
DefaultFileSystem fs = sensorContextTester.fileSystem();
fs.setWorkDir(invalidWorkDirectory);
SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, fs, mock(ClasspathForMain.class), mock(ClasspathForTest.class),
checkFactory, context.activeRules());

assertThatThrownBy(() -> sonarComponents.getAutoScanClasspath(pluginFile))
.isInstanceOf(AnalysisException.class)
.hasMessage("Failed to prepare the Java analyzer classpath for AutoScan.")
.hasCauseInstanceOf(IOException.class);
}

private static Path createPluginJar(Path pluginJar) throws IOException {
List<String> entries = List.of(
"org/sonar/java/Analyzer.class",
"javax/servlet/Servlet.class",
"jakarta/servlet/Servlet.class",
"jakarta/el/ELContext.class"
);
try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(pluginJar))) {
for (String entry : entries) {
jar.putNextEntry(new JarEntry(entry));
jar.write(entry.getBytes(StandardCharsets.UTF_8));
jar.closeEntry();
}
}
return pluginJar;
}

@Test
void autoscan_getters() {
MapSettings settings = new MapSettings();
Expand Down
5 changes: 3 additions & 2 deletions java-jsp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.120</version>
<version>11.0.24</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.jdt</groupId>
Expand Down Expand Up @@ -92,7 +92,8 @@
<artifactItem>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.3.RELEASE</version>
<version>6.2.15</version>
<destFileName>spring-webmvc.jar</destFileName>
</artifactItem>
<artifactItem>
<groupId>javax</groupId>
Expand Down
3 changes: 1 addition & 2 deletions java-jsp/src/main/java/org/sonar/java/jsp/Jasper.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;
import javax.servlet.jsp.JspFactory;
import jakarta.servlet.jsp.JspFactory;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.jasper.compiler.Compiler;
Expand Down Expand Up @@ -254,4 +254,3 @@ public void log(String message, Throwable exception) {
}

}

9 changes: 1 addition & 8 deletions java-jsp/src/main/java/org/sonar/java/jsp/JasperOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
import javax.servlet.jsp.tagext.TagLibraryInfo;
import jakarta.servlet.jsp.tagext.TagLibraryInfo;
import org.apache.jasper.Options;
import org.apache.jasper.TrimSpacesOption;
import org.apache.jasper.compiler.JspConfig;
Expand All @@ -40,8 +40,6 @@
*/
class JasperOptions implements Options {

static final String DEFAULT_IE_CLASS_ID = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93";

private final Path outputDir;
private final TagPluginManager tagPluginManager;
private final TldCache tldCache;
Expand Down Expand Up @@ -123,11 +121,6 @@ public TrimSpacesOption getTrimSpaces() {
return TrimSpacesOption.FALSE;
}

@Override
public String getIeClassId() {
return DEFAULT_IE_CLASS_ID;
}

@Override
public File getScratchDir() {
return outputDir.toFile();
Expand Down
Loading
Loading