package com.siberika.idea.pascal.lang.compiled; import com.google.common.base.Joiner; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.BinaryFileDecompiler; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.vfs.VirtualFile; import com.siberika.idea.pascal.DCUFileType; import com.siberika.idea.pascal.PascalBundle; import com.siberika.idea.pascal.PascalException; import com.siberika.idea.pascal.sdk.BasePascalSdkType; import com.siberika.idea.pascal.sdk.DelphiSdkType; import com.siberika.idea.pascal.util.ModuleUtil; import com.siberika.idea.pascal.util.SysUtils; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Author: George Bakhtadze * Date: 21/05/2015 */ public class DCUFileDecompiler implements BinaryFileDecompiler { private static final Logger LOG = Logger.getInstance(DCUFileDecompiler.class); private static final Pattern WARNING1 = Pattern.compile("Warning:.+- all imported names will be shown with unit names"); private static final Pattern WARNING2 = Pattern.compile("Warning at 0x[A-F0-9]+.*"); private static final Pattern CONSTANT1 = Pattern.compile("\\s*[A-F0-9]+:\\s*.+(\\||\\[)[A-F0-9 (]+\\|.*"); private static final Pattern CONSTANT2 = Pattern.compile("\\s*raw\\s*\\[\\$[0-9A-F]+\\.\\.\\$[0-9A-F]+\\]\\s*at \\$[0-9A-F]+"); private static final Pattern VAR = Pattern.compile("\\s*spec var\\s+\\w+\\.\\$\\w+.*"); private static final Pattern TYPE = Pattern.compile("\\s*\\w+\\.\\w+\\s*=.*"); private static final Pattern COMMENTED_TYPE = Pattern.compile("\\s*\\{type}\\s*"); private static final Pattern ROUTINE = Pattern.compile("(\\s*)(procedure|function|operator)(\\s+)(@)(\\w+)"); private static final Pattern INLINE_TYPE = Pattern.compile("\\s*:\\d+\\s+=\\s+.*"); private static final File NULL_FILE = new File(""); @NotNull @Override public CharSequence decompile(VirtualFile file) { assert file.getFileType() == DCUFileType.INSTANCE; final Project[] projects = ProjectManager.getInstance().getOpenProjects(); if (projects.length == 0) return ""; final Project project = projects[0]; return decompileText(project, file); } static String decompileText(Project project, VirtualFile file) { Module module = ModuleUtil.getModuleForLibraryFile(project, file); if (null == module) { return PascalBundle.message("decompile.no.module", file.getPath()); } Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); if (null == sdk) { return PascalBundle.message("decompile.wrong.sdk"); } if ((sdk.getHomePath() == null) || !(sdk.getSdkType() instanceof DelphiSdkType)) { return PascalBundle.message("decompile.wrong.sdktype.delphi"); } File decompilerCommand = BasePascalSdkType.getDecompilerCommand(sdk, NULL_FILE); String result = ""; try { if (!decompilerCommand.isFile() || !decompilerCommand.canExecute()) { return PascalBundle.message("decompile.wrong.delphi", decompilerCommand.getCanonicalPath()); } List<String> paths = collectUnitPaths(sdk); String[] args = getArgs(BasePascalSdkType.getDecompilerArgs(sdk), file.getPath(), "-U" + Joiner.on(';').join(paths), "-I", "-SI", "-"); result = SysUtils.runAndGetStdOut(sdk.getHomePath(), decompilerCommand.getCanonicalPath(), args); if (result != null) { return handleText(result); } else { return PascalBundle.message("decompile.empty.result"); } } catch (IOException e) { LOG.info("I/O error: " + e.getMessage(), e); return PascalBundle.message("decompile.io.error"); } catch (PascalException e1) { return e1.getMessage(); } catch (Exception e) { LOG.info("Unknown error: " + e.getMessage(), e); return PascalBundle.message("decompile.unknown.error", result); } } private static String[] getArgs(String[] argsArray, String...args) { String[] res = new String[args.length + argsArray.length]; int i = 0; for (String arg : args) { res[i++] = arg; } for (String arg : argsArray) { res[i++] = arg; } return res; } private static List<String> collectUnitPaths(Sdk sdk) { VirtualFile[] sdkFiles = sdk.getRootProvider().getFiles(OrderRootType.CLASSES); Set<File> paths = com.siberika.idea.pascal.jps.util.FileUtil.retrievePaths(sdkFiles); List<String> result = new ArrayList<String>(paths.size()); for (File path : paths) { result.add(path.getAbsolutePath()); } return result; } private static String handleText(@NotNull String result) { String[] lines = result.split("\n"); boolean unitDone = false; boolean inConst = false; StringBuffer res = new StringBuffer(); for (String line : lines) { if (isConstant(line)) { // Comment out all non-compilable constant declarations if (!inConst) { res.append(" default;\n"); // insert const value inConst = true; } res.append("// "); } else { inConst = false; } if (shouldCommentOut(line)) { // Comment out all decompiler warnings res.append("// "); } else if (!unitDone) { // Comment out all lines before unit declaration if (line.startsWith("unit")) { unitDone = true; } else { res.append("// "); } } if (isType(line)) { res.append("__").append(line.trim()); } else if (COMMENTED_TYPE.matcher(line).matches()) { res.append(" type\n "); } else if (INLINE_TYPE.matcher(line).matches()) { res.append(line.replaceFirst(":", "_")); } else { Matcher m = ROUTINE.matcher(line); if (m.find()) { m.appendReplacement(res, "$1$2$3$5"); m.appendTail(res).append("\n"); } else if (!line.startsWith("procedure Finalization")) { res.append(line).append("\n"); } } } res.append("implementation\n {compiled code}\nend.\n"); return res.toString(); } private static boolean shouldCommentOut(String line) { return isWarning(line) || isVar(line); } private static boolean isConstant(String line) { return CONSTANT1.matcher(line).matches() || CONSTANT2.matcher(line).matches(); } private static boolean isWarning(String line) { return WARNING1.matcher(line).matches() || WARNING2.matcher(line).matches(); } private static boolean isType(String line) { return TYPE.matcher(line).matches(); } private static boolean isVar(String line) { return VAR.matcher(line).matches(); } }