diff --git a/src/main/java/org/web3j/console/ContractAuditor.java b/src/main/java/org/web3j/console/ContractAuditor.java index 730327d..92df614 100644 --- a/src/main/java/org/web3j/console/ContractAuditor.java +++ b/src/main/java/org/web3j/console/ContractAuditor.java @@ -12,21 +12,202 @@ */ package org.web3j.console; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPathFactory; + +import ru.smartdec.smartcheck.RulesCached; +import ru.smartdec.smartcheck.RulesXml; +import ru.smartdec.smartcheck.app.DirectoryAnalysis; +import ru.smartdec.smartcheck.app.DirectoryAnalysisCombined; +import ru.smartdec.smartcheck.app.DirectoryAnalysisDefault; +import ru.smartdec.smartcheck.app.Media; +import ru.smartdec.smartcheck.app.ReportDefault; +import ru.smartdec.smartcheck.app.SourceLanguage; +import ru.smartdec.smartcheck.app.SourceLanguages; +import ru.smartdec.smartcheck.app.TreeFactoryDefault; +import ru.smartdec.smartcheck.app.cli.Tool; + import static org.web3j.codegen.Console.exitError; public class ContractAuditor { private static final String USAGE = "audit "; + private static DirectoryAnalysis makeDirectoryAnalysis( + final SourceLanguage sourceLanguage, + final Path source, + final Function rules) + throws Exception { + return new DirectoryAnalysisDefault( + source, + p -> p.toString().endsWith(sourceLanguage.fileExtension()), + new TreeFactoryDefault( + DocumentBuilderFactory.newInstance().newDocumentBuilder(), sourceLanguage), + new RulesCached( + new RulesXml( + rules.apply(sourceLanguage), + XPathFactory.newInstance().newXPath(), + Throwable::printStackTrace))); + } + public static void main(String[] args) { if (args.length != 1) { exitError(USAGE); } try { - ru.smartdec.smartcheck.app.cli.Tool.main(new String[] {"-p", args[0]}); + Path source = Paths.get(args[0]); + Function defaultRules = + sourceLanguage -> + () -> { + String rulesFileName = sourceLanguage.rulesFileName(); + URI uri = RulesXml.class.getResource(rulesFileName).toURI(); + try { + HashMap env = new HashMap<>(); + env.put("create", "true"); + FileSystems.newFileSystem(uri, env); + } catch (FileSystemAlreadyExistsException ignored) { + } + return Paths.get(uri); + }; + + final Integer[] totals = {0, 0}; + DefaultMedia media = new DefaultMedia(totals); + new ReportDefault( + new DirectoryAnalysisCombined( + makeDirectoryAnalysis( + new SourceLanguages.Solidity(), source, defaultRules), + makeDirectoryAnalysis( + new SourceLanguages.Vyper(), source, defaultRules)), + media) + .print(); + + if (media.getTotals()[1] > 0) { + System.exit(-1); + } } catch (Exception e) { System.err.println("The audit operation failed with the following exception:"); e.printStackTrace(); } } } + +class DefaultMedia implements Media { + + Integer[] getTotals() { + return totals; + } + + private final Integer[] totals; + + DefaultMedia(final Integer[] totals) { + this.totals = totals; + } + + @Override + public void accept(final DirectoryAnalysis.Info info) { + LinkedList> report_fields = new LinkedList<>(); + Map result = new HashMap<>(); + info.treeReport() + .streamUnchecked() + .forEach( + tree -> + tree.contexts() + .forEach( + context -> { + LinkedList fields = new LinkedList<>(); + String rule_name; + try { + URL rule_name_resource = + Tool.class + .getClassLoader() + .getResource( + String.format( + "rule_descriptions/%s/name_en.txt", + tree.rule() + .id())); + if (rule_name_resource != null) { + rule_name = + new String( + Files.readAllBytes( + Paths.get( + rule_name_resource + .toURI()))); + } else { + rule_name = ""; + } + } catch (IOException | URISyntaxException e) { + rule_name = ""; + } + fields.addLast(""); + fields.addLast( + String.format( + "%d:%d", + context.getStart().getLine(), + context.getStart() + .getCharPositionInLine())); + fields.addLast( + String.format( + "severity:%d", + tree.pattern().severity())); + if (tree.pattern().severity() > 1) { + totals[1]++; + } + fields.addLast(rule_name); + fields.addLast( + String.format( + "%s_%s", + tree.rule().id(), + tree.pattern().id())); + result.compute( + tree.rule().id(), + (k, v) -> + Optional.ofNullable(v) + .map(i -> i + 1) + .orElse(1)); + report_fields.addLast(fields); + })); + if (!report_fields.isEmpty()) { + System.out.println(info.file()); + System.out.print(formatAsTable(report_fields)); + totals[0] += report_fields.size(); + } + } + + private static String formatAsTable(List> rows) { + if (rows.isEmpty()) return ""; + int[] maxLengths = new int[rows.get(0).size()]; + for (List row : rows) { + for (int i = 0; i < row.size(); i++) { + maxLengths[i] = Math.max(maxLengths[i], row.get(i).length()); + } + } + + StringBuilder formatBuilder = new StringBuilder(); + for (int maxLength : maxLengths) { + formatBuilder.append("%-").append(maxLength + 3).append("s"); + } + String format = formatBuilder.toString(); + + StringBuilder result = new StringBuilder(); + for (List row : rows) { + String[] res = row.toArray(new String[0]); + result.append(String.format(format, (Object[]) res)).append("\n"); + } + return result.toString(); + } +}