Browse Source

Merge pull request #749 from Squamanto/experimental-source-analysis

Experimental source analysis
Timur Sağlam 4 years ago
parent
commit
ed3927d117

+ 75 - 0
languages/cpp/src/main/java/de/jplag/cpp/experimental/GCCSourceAnalysis.java

@@ -0,0 +1,75 @@
+package de.jplag.cpp.experimental;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Uses GCC to find unused variables and saves their location. The scanner can then check if a token belongs to an
+ * unused variable
+ */
+public class GCCSourceAnalysis implements SourceAnalysis {
+
+    public static final String COMPILE_COMMAND = "gcc -Wall -fsyntax-only %s";
+    private Map<String, List<Integer>> linesToDelete = new HashMap<>();
+
+    private final Logger logger;
+
+    public GCCSourceAnalysis() {
+        this.logger = LoggerFactory.getLogger(this.getClass());
+    }
+
+    public boolean isTokenIgnored(de.jplag.cpp.Token token, File file) {
+        String fileName = file.getName();
+        if (linesToDelete.containsKey(fileName)) {
+            var ignoredLineNumbers = linesToDelete.get(fileName);
+            return ignoredLineNumbers.contains(token.beginLine);
+        }
+        return false;
+    }
+
+    public void findUnusedVariableLines(Set<File> files) throws InterruptedException {
+        linesToDelete = new HashMap<>();
+
+        for (File file : files) {
+            try {
+                Runtime runtime = Runtime.getRuntime();
+                Process gcc = runtime.exec(COMPILE_COMMAND.formatted(file.getAbsolutePath()));
+                gcc.waitFor();
+
+                // gcc prints compiler warnings to the error stream, not the standard stream
+                BufferedReader stdError = new BufferedReader(new InputStreamReader(gcc.getErrorStream()));
+
+                String line;
+                while ((line = stdError.readLine()) != null) {
+                    processOutputLine(line);
+                }
+            } catch (IOException e) {
+                // error during compilation, skip this submission
+                logger.warn("Failed to compile file {}", file.getAbsolutePath());
+            }
+        }
+    }
+
+    private void processOutputLine(String line) {
+        // example output:
+        // sourceFile.c:151:8: warning: unused variable 't' [-Wunused-variable]
+        if (!line.contains("unused variable")) {
+            return;
+        }
+
+        // contains [sourceFile, line, column, (warning|error), description]
+        var lineSplit = line.split(":");
+
+        String fileName = new File(lineSplit[0]).getName();
+
+        int lineNumber = Integer.parseInt(lineSplit[1]);
+
+        linesToDelete.computeIfAbsent(fileName, key -> new ArrayList<>()).add(lineNumber);
+    }
+}

+ 26 - 0
languages/cpp/src/main/java/de/jplag/cpp/experimental/SourceAnalysis.java

@@ -0,0 +1,26 @@
+package de.jplag.cpp.experimental;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Strategy for analyzing source code for unused variables querying the corresponding lines
+ */
+public interface SourceAnalysis {
+
+    /**
+     * Tells the caller if a token is located in a line containing an unused variable. This usually indicates that the token
+     * belongs to a declaration of an unused variable. An edge case is multiple variable declarations in a single line, e.g.
+     * 'int a, b;' where a is used an b is unused.
+     * @param token The token that will be checked
+     * @param file The file the token was scanned in
+     * @return True, if the token should not be added to a TokenList, false if it should
+     */
+    boolean isTokenIgnored(de.jplag.cpp.Token token, File file);
+
+    /**
+     * Executes the source analysis on the files of a submission.
+     * @param files Set of the files contained in the submission
+     */
+    void findUnusedVariableLines(Set<File> files) throws InterruptedException;
+}