Skip to content

Commit

Permalink
start a new process running the javac wrapper as javac, and use the a…
Browse files Browse the repository at this point in the history
…rguments generated by CheckerMain to launch it
  • Loading branch information
zhangjiangqige committed Oct 27, 2019
1 parent 23906bc commit d328a02
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 36 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ repositories {
}

dependencies {
implementation "org.checkerframework:javacutil:2.9.0"
compile group: 'org.checkerframework', name: 'checker', version: '2.11.1'
implementation "org.checkerframework:javacutil:2.11.1"
implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.7.2"
implementation 'com.google.code.gson:gson:2.8.5'
testImplementation 'junit:junit:4.12'
Expand Down
3 changes: 3 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

./gradlew shadowjar
144 changes: 144 additions & 0 deletions src/main/java/org/checkerframework/languageserver/CFDiagnostic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package org.checkerframework.languageserver;

import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.util.Locale;

public class CFDiagnostic implements Diagnostic {

final String fileUri;
final String kind;
final long position;
final long startPosition;
final long endPosition;
final long lineNumber;
final long columnNumber;
final String code;
final String message;

CFDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) {
fileUri = diagnostic.getSource().toUri().toString();
kind = diagnostic.getKind().name();
position = diagnostic.getPosition();
startPosition = diagnostic.getStartPosition();
endPosition = diagnostic.getEndPosition();
lineNumber = diagnostic.getLineNumber();
columnNumber = diagnostic.getColumnNumber();
code = diagnostic.getCode();
message = diagnostic.getMessage(null);
}

/**
* Gets the kind of this diagnostic, for example, error or
* warning.
*
* @return the kind of this diagnostic
*/
@Override
public Kind getKind() {
return Kind.valueOf(kind);
}

/**
* Gets the source object associated with this diagnostic.
*
* @return the source object associated with this diagnostic.
* {@code null} if no source object is associated with the
* diagnostic.
*/
@Override
public Object getSource() {
return fileUri;
}

/**
* Gets a character offset from the beginning of the source object
* associated with this diagnostic that indicates the location of
* the problem. In addition, the following must be true:
*
* <p>{@code getStartPostion() <= getPosition()}
* <p>{@code getPosition() <= getEndPosition()}
*
* @return character offset from beginning of source; {@link
* #NOPOS} if {@link #getSource()} would return {@code null} or if
* no location is suitable
*/
@Override
public long getPosition() {
return position;
}

/**
* Gets the character offset from the beginning of the file
* associated with this diagnostic that indicates the start of the
* problem.
*
* @return offset from beginning of file; {@link #NOPOS} if and
* only if {@link #getPosition()} returns {@link #NOPOS}
*/
@Override
public long getStartPosition() {
return startPosition;
}

/**
* Gets the character offset from the beginning of the file
* associated with this diagnostic that indicates the end of the
* problem.
*
* @return offset from beginning of file; {@link #NOPOS} if and
* only if {@link #getPosition()} returns {@link #NOPOS}
*/
@Override
public long getEndPosition() {
return endPosition;
}

/**
* Gets the line number of the character offset returned by
* {@linkplain #getPosition()}.
*
* @return a line number or {@link #NOPOS} if and only if {@link
* #getPosition()} returns {@link #NOPOS}
*/
@Override
public long getLineNumber() {
return lineNumber;
}

/**
* Gets the column number of the character offset returned by
* {@linkplain #getPosition()}.
*
* @return a column number or {@link #NOPOS} if and only if {@link
* #getPosition()} returns {@link #NOPOS}
*/
@Override
public long getColumnNumber() {
return columnNumber;
}

/**
* Gets a diagnostic code indicating the type of diagnostic. The
* code is implementation-dependent and might be {@code null}.
*
* @return a diagnostic code
*/
@Override
public String getCode() {
return code;
}

/**
* Gets a localized message for the given locale. The actual
* message is implementation-dependent. If the locale is {@code
* null} use the default locale.
*
* @param locale a locale; might be {@code null}
* @return a localized message
*/
@Override
public String getMessage(Locale locale) {
return message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private void clearDiagnostics(List<File> files) {
/**
* Convert raw diagnostics from the compiler to the LSP counterpart.
*/
private Diagnostic convertToLSPDiagnostic(javax.tools.Diagnostic<? extends JavaFileObject> diagnostic) {
private Diagnostic convertToLSPDiagnostic(javax.tools.Diagnostic diagnostic) {
DiagnosticSeverity severity;
switch (diagnostic.getKind()) {
case ERROR:
Expand Down Expand Up @@ -96,10 +96,10 @@ private Diagnostic convertToLSPDiagnostic(javax.tools.Diagnostic<? extends JavaF
* @param files source files to be checked
*/
private void checkAndPublish(List<File> files) {
Map<JavaFileObject, List<javax.tools.Diagnostic<? extends JavaFileObject>>> result = executor.compile(files);
for (Map.Entry<JavaFileObject, List<javax.tools.Diagnostic<? extends JavaFileObject>>> entry: result.entrySet()) {
Map<String, List<javax.tools.Diagnostic>> result = executor.compile(files);
for (Map.Entry<String, List<javax.tools.Diagnostic>> entry: result.entrySet()) {
server.publishDiagnostics(new PublishDiagnosticsParams(
entry.getKey().toUri().toString(),
entry.getKey(),
entry.getValue().stream().map(this::convertToLSPDiagnostic).collect(Collectors.toList())
));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package org.checkerframework.languageserver;

import javax.tools.*;
import java.io.File;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import com.google.gson.Gson;
import org.checkerframework.framework.util.CheckerMain;

/**
* Used to run the checker framework and collect results.
Expand All @@ -19,27 +16,43 @@ class CheckExecutor {
private static final Logger logger = Logger.getLogger(CheckExecutor.class.getName());

private final List<String> options;
private final JavaCompiler compiler;
private StandardJavaFileManager fileManager;
private final Gson gson;

CheckExecutor(String jdkPath, String checkerPath, List<String> checkers, List<String> commandLineOptions) {
compiler = ToolProvider.getSystemJavaCompiler();
fileManager = compiler.getStandardFileManager(null, null, null);

List<String> opts = new ArrayList<>();
// adapted from checker-framework/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java
options = new ArrayList<>();
// Even though the method compiler.getTask takes a list of processors, it fails if
// processors are passed this way with the message:
// error: Class names, 'org.checkerframework.checker.interning.InterningChecker', are only
// accepted if annotation processing is explicitly requested
// Therefore, we now add them to the beginning of the options list.
options.add("-processor");
options.add(String.join(",", checkers));
options.add("-Xbootclasspath/p:" + jdkPath);
options.add("-processorpath");
options.add(checkerPath);
options.add("-proc:only");
options.addAll(commandLineOptions);
opts.add("-processor");
opts.add(String.join(",", checkers));
opts.add("-Xbootclasspath/p:" + jdkPath);
opts.add("-processorpath");
opts.add(checkerPath);
opts.add("-proc:only");
opts.addAll(commandLineOptions);

CheckerMain cm = new CheckerMain(new File(checkerPath), opts);
options = new ArrayList<>();
boolean sawClasspath = false;
Iterator<String> it = cm.getExecArguments().iterator();
while (it.hasNext()) {
String o = it.next();
if (o.equals("com.sun.tools.javac.Main")) {
options.add(JavacWrapper.class.getCanonicalName());
} else if (!sawClasspath && (o.equals("-cp") || o.equals("-classpath"))) {
sawClasspath = true;
options.add(o);
o = it.next();
options.add(o + ":" + JavacWrapper.class.getProtectionDomain().getCodeSource().getLocation().getPath());
} else {
options.add(o);
}
}

gson = new Gson();
}

/**
Expand All @@ -48,22 +61,41 @@ class CheckExecutor {
* @param files the files to be checked
* @return raw diagnostics grouped by JavaFileObject
*/
Map<JavaFileObject, List<Diagnostic<? extends JavaFileObject>>> compile(List<File> files) {
Map<String, List<Diagnostic>> compile(List<File> files) {
if (files.isEmpty())
return Collections.emptyMap();

files.forEach(f -> logger.info("checking: " + f.getPath()));
files.forEach(f -> logger.info("checking: " + f));

List<String> opts = new ArrayList<>(options);
Map<String, List<Diagnostic>> ret = null;

try {
for (File f: files) {
opts.add(f.getCanonicalPath());
}
StringBuilder sb = new StringBuilder();
for (String s: opts) {
sb.append(s).append(" ");
}
logger.info(this.getClass().getName() + " javac options: " + sb.toString());

DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
Iterable<? extends JavaFileObject> javaFiles = fileManager.getJavaFileObjectsFromFiles(files);
Process proc = Runtime.getRuntime().exec(opts.toArray(new String[0]));
proc.waitFor();
DiagnosticList diags = gson.fromJson(new InputStreamReader(proc.getInputStream()), DiagnosticList.class);

compiler
.getTask(new StringWriter(), fileManager, diagnostics, options, new ArrayList<String>(), javaFiles)
.call();
ret = new HashMap<>();
for (CFDiagnostic d: diags.diags) {
String s = (String)d.getSource();
if (!ret.containsKey(s)) {
ret.put(s, new ArrayList<>());
}
ret.get(s).add(d);
}
} catch (Exception e) {
logger.info(e.toString());
}

return diagnostics
.getDiagnostics()
.stream()
.collect(Collectors.groupingBy(Diagnostic::getSource));
return ret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.checkerframework.languageserver;

import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.util.ArrayList;
import java.util.List;

public class DiagnosticList {

final List<CFDiagnostic> diags;

public DiagnosticList(List<Diagnostic<? extends JavaFileObject>> diagnostics) {
diags = new ArrayList<>();
for (Diagnostic<? extends JavaFileObject> d: diagnostics) {
diags.add(new CFDiagnostic(d));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.checkerframework.languageserver;

import com.google.gson.Gson;

import javax.tools.*;
import java.util.ArrayList;
import java.util.List;

/**
* This class wraps around javac (com.sun.tools.javac) in order to output diagnostics.
* It passes parameters transparently to javac, and so for other classes it behaves exactly the same as javac and can
* substitute com.sun.tools.javac.Main.
*/
public class JavacWrapper {

public static void main(String[] args) {
JavacWrapper javacw = new JavacWrapper();
javacw.compile(args);
}

public void compile(String[] args) {
List<String> options = new ArrayList<>();
List<String> sourcefiles = new ArrayList<>();
separateOptionsAndFiles(args, options, sourcefiles);

JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

Iterable<? extends JavaFileObject> javaFiles = fileManager.getJavaFileObjectsFromStrings(sourcefiles);

javac
.getTask(null, null, diagnostics, options, null, javaFiles)
.call();

DiagnosticList diags = new DiagnosticList(diagnostics.getDiagnostics());
System.out.println(new Gson().toJson(diags, DiagnosticList.class));
}

private void separateOptionsAndFiles(String[] args, List<String> options, List<String> sourcefiles) {
for (String a: args) {
if (a.endsWith(".java"))
sourcefiles.add(a);
else options.add(a);
}
}
}

0 comments on commit d328a02

Please sign in to comment.