lyxell / logifix

Fixing static analysis violations in Java source code using Datalog
BSD 2-Clause "Simplified" License
17 stars 0 forks source link
datalog program-repair program-transformation static-analysis
Logifix

Logifix

Logifix is a Datalog-based tool for automatically fixing static analysis violations in Java source code. Logifix can be used to fix static analysis violations for static analyzers such as SonarQube, PMD or SpotBugs, but also to modernize legacy code or even to automatically enforce custom rules specific to your code base.

Demo

https://user-images.githubusercontent.com/4975941/126644571-3215f01b-40f6-4278-9752-fe988c5e0367.mp4

Getting started

1. Install

Prebuilt and dependency-free binaries are provided for macOS and GNU-based Linux systems.

Ubuntu/Debian
curl -L https://github.com/lyxell/logifix/releases/latest/download/logifix-x86_64-linux-gnu.gz | gunzip -c - > /tmp/logifix
chmod +x /tmp/logifix
sudo mv /tmp/logifix /usr/local/bin
macOS
curl -L https://github.com/lyxell/logifix/releases/latest/download/logifix-x86_64-macos.gz | gunzip -c - > /tmp/logifix
chmod +x /tmp/logifix
sudo mv /tmp/logifix /usr/local/bin

2. Run

What is Logifix?

Logifix is an analysis-guided rewrite system for Java source code. This means that you define (or use predefined) analyses and transformations that all work together to improve your code. The analyses and transformations are written in the highly declarative logic-based language Datalog and are combined and chained automatically by the rewrite engine.

Features

Intelligent equational reasoning

Logifix is more than a search-and-replace system. It performs rewrites in multiple steps and can achieve intelligent equational reasoning by building articulation points in the rewrite graph.

Speed

Logifix is implemented in a high-performance Datalog dialect that is synthesized into multi-threaded C++ code. It is heavily parallelized even when working on a single file and usually analyzes large projects of thousands of files in a few seconds on modern hardware. If your project is slow to analyze it is considered a bug and you should file a bug report.

Mergeability

Logifix is engineered to produce human-like patches that are ready-to-merge by design without requiring manual modifications.

Examples

Here is a few examples of patches generated by Logifix.

Fix inefficient iteration over Map#keySet

  public static void sign(String token, Map<String, String> params) {
      List<String> list = new ArrayList();
      String tokenClientSlat = "";
-     for (String key : params.keySet()) {
+     for (Map.Entry<String, String> entry : params.entrySet()) {
+         String key = entry.getKey();
          if (key.equals("token_client_salt")) {
-             tokenClientSlat = params.get(key);
+             tokenClientSlat = entry.getValue();
          }
-         String paramString = key + "=" + params.get(key);
+         String paramString = key + "=" + entry.getValue();
          list.add(paramString);
      }
      Collections.sort(list);

Simplify code using try-with-resources

          continue;
      } 
-     try {
-         JarFile nextJarFile = new JarFile(absNextFile);
-         try {
-             Attributes attrs = getMainAttrs(nextJarFile);
-             Set<Extension> newExtensions = getReferencedExtensions(attrs);
-             result.addAll(newExtensions);
-             filesToProcess.addAll(extensionsToFiles(newExtensions));
-         } finally {
-             nextJarFile.close();
-         }
+     try (JarFile nextJarFile = new JarFile(absNextFile)) {
+         Attributes attrs = getMainAttrs(nextJarFile);
+         Set<Extension> newExtensions = getReferencedExtensions(attrs);
+         result.addAll(newExtensions);
+         filesToProcess.addAll(extensionsToFiles(newExtensions));
      } catch (Exception e) {
          invalidLibPaths.append(nextFile.getPath()).append(" ");
      }

Simplify code using Map::computeIfAbsent

  private final Map<Class<?>, String> requestQueueNameCache = new ConcurrentHashMap<>();

  public String getRequestQueueName(Class<?> remoteInterface) {
-     String str = requestQueueNameCache.get(remoteInterface);
-     if (str == null) {
-         str = "{" + name + ":" + remoteInterface.getName() + "}";
-         requestQueueNameCache.put(remoteInterface, str);
-     }
-     return str;
+     return requestQueueNameCache.computeIfAbsent(remoteInterface, k -> "{" + name + ":" + k.getName() + "}");
  }

Simplify code using streams

  @Override
  public List<SpoonFile> getAllJavaFiles() {
-     List<SpoonFile> result = new ArrayList<>();
-
-     for (SpoonFile f : getAllFiles()) {
-         if (f.isJava()) {
-             result.add(f);
-         }
-     }
-
-     return result;
+     return getAllFiles().stream().filter(SpoonFile::isJava).collect(Collectors.toList());
  } 

FAQ

Where can I find the predefined transformations?

See docs/predefined-transformations.md or the source code src/rules.

Can I create my own transformations?

Yes! See docs/creating-your-own-transformations.md.

How do I build from source?

See docs/building.md.

Related projects

If you find this project interesting, be sure to check out these as well: