-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #147 from pontem-network/aptos-decompiler
Aptos decompiler
- Loading branch information
Showing
44 changed files
with
1,354 additions
and
356 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/main/kotlin/org/move/bytecode/AptosBytecodeFileType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.move.bytecode | ||
|
||
import com.intellij.openapi.fileTypes.FileType | ||
import org.move.ide.MoveIcons | ||
|
||
object AptosBytecodeFileType: FileType { | ||
override fun getIcon() = MoveIcons.MV_LOGO | ||
override fun getName() = "APTOS_BYTECODE" | ||
override fun getDefaultExtension() = "mv" | ||
override fun getDescription() = "Aptos Move bytecode" | ||
override fun getDisplayName() = "Aptos Move bytecode" | ||
override fun isBinary(): Boolean = true | ||
} |
117 changes: 117 additions & 0 deletions
117
src/main/kotlin/org/move/bytecode/AptosBytecodeNotificationProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package org.move.bytecode | ||
|
||
import com.intellij.ide.util.PropertiesComponent | ||
import com.intellij.notification.NotificationType.ERROR | ||
import com.intellij.openapi.fileEditor.FileEditor | ||
import com.intellij.openapi.fileTypes.FileTypeRegistry | ||
import com.intellij.openapi.progress.ProgressIndicator | ||
import com.intellij.openapi.progress.ProgressManager | ||
import com.intellij.openapi.progress.Task | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.vfs.VirtualFile | ||
import com.intellij.openapi.vfs.VirtualFileManager | ||
import com.intellij.openapi.vfs.newvfs.BulkFileListener | ||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent | ||
import com.intellij.ui.EditorNotificationPanel | ||
import com.intellij.ui.EditorNotificationProvider | ||
import org.move.ide.notifications.showDebugBalloon | ||
import org.move.ide.notifications.updateAllNotifications | ||
import org.move.openapiext.openFile | ||
import org.move.openapiext.pathAsPath | ||
import org.move.stdext.RsResult | ||
import org.move.stdext.unwrapOrElse | ||
import java.util.function.Function | ||
import javax.swing.JComponent | ||
|
||
class AptosBytecodeNotificationProvider(project: Project): EditorNotificationProvider { | ||
|
||
init { | ||
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object: BulkFileListener { | ||
override fun after(events: MutableList<out VFileEvent>) { | ||
updateAllNotifications(project) | ||
} | ||
}) | ||
} | ||
|
||
override fun collectNotificationData( | ||
project: Project, | ||
file: VirtualFile | ||
): Function<in FileEditor, out JComponent?>? { | ||
if (!FileTypeRegistry.getInstance().isFileOfType(file, AptosBytecodeFileType)) { | ||
return null | ||
} | ||
val properties = PropertiesComponent.getInstance(project) | ||
val decompilationFailedKey = DECOMPILATION_FAILED + "." + file.path | ||
|
||
val aptosDecompiler = AptosBytecodeDecompiler() | ||
val decompiledFilePath = file.parent.pathAsPath.resolve(aptosDecompiler.sourceFileName(file)) | ||
val decompilationTask = DecompilationModalTask(project, file) | ||
|
||
return Function { | ||
EditorNotificationPanel(it).apply { | ||
val existingDecompiledFile = | ||
VirtualFileManager.getInstance().refreshAndFindFileByNioPath(decompiledFilePath) | ||
if (existingDecompiledFile != null) { | ||
// file exists | ||
text = "Decompiled source file exists" | ||
createActionLabel("Open source file") { | ||
project.openFile(existingDecompiledFile) | ||
} | ||
return@apply | ||
} | ||
|
||
// decompiledFile does not exist | ||
val decompilationFailed = properties.getBoolean(decompilationFailedKey, false) | ||
if (decompilationFailed) { | ||
text = "Decompilation command failed" | ||
createActionLabel("Try again") { | ||
val virtualFile = decompilationTask.runWithProgress() | ||
.unwrapOrElse { | ||
// something went wrong with the decompilation command again | ||
project.showDebugBalloon("Error with decompilation process", it, ERROR) | ||
return@createActionLabel | ||
} | ||
|
||
properties.setValue(decompilationFailedKey, false) | ||
project.openFile(virtualFile) | ||
updateAllNotifications(project) | ||
} | ||
} else { | ||
createActionLabel("Decompile into source code") { | ||
val decompiledFile = decompilationTask.runWithProgress() | ||
.unwrapOrElse { | ||
project.showDebugBalloon("Error with decompilation process", it, ERROR) | ||
return@unwrapOrElse null | ||
} | ||
if (decompiledFile == null) { | ||
// something went wrong with the decompilation command | ||
properties.setValue(decompilationFailedKey, true) | ||
} else { | ||
project.openFile(decompiledFile) | ||
} | ||
updateAllNotifications(project) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
class DecompilationModalTask(project: Project, val file: VirtualFile): | ||
Task.WithResult<RsResult<VirtualFile, String>, Exception>( | ||
project, | ||
"Decompiling ${file.name}...", | ||
true | ||
) { | ||
override fun compute(indicator: ProgressIndicator): RsResult<VirtualFile, String> { | ||
val aptosDecompiler = AptosBytecodeDecompiler() | ||
return aptosDecompiler.decompileFileToTheSameDir(project, file) | ||
} | ||
|
||
fun runWithProgress(): RsResult<VirtualFile, String> = ProgressManager.getInstance().run(this) | ||
} | ||
|
||
companion object { | ||
private const val DECOMPILATION_FAILED = "org.move.aptosDecompilerNotificationKey" | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package org.move.bytecode | ||
|
||
import com.intellij.openapi.Disposable | ||
import com.intellij.openapi.fileEditor.impl.LoadTextUtil | ||
import com.intellij.openapi.fileTypes.BinaryFileDecompiler | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.util.Disposer | ||
import com.intellij.openapi.util.io.FileUtil | ||
import com.intellij.openapi.util.text.StringUtil | ||
import com.intellij.openapi.vfs.* | ||
import com.intellij.openapi.vfs.newvfs.BulkFileListener | ||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent | ||
import org.move.cli.settings.getAptosCli | ||
import org.move.openapiext.pathAsPath | ||
import org.move.openapiext.rootDisposable | ||
import org.move.openapiext.rootPath | ||
import org.move.stdext.RsResult | ||
import org.move.stdext.unwrapOrElse | ||
import java.io.File | ||
import java.nio.file.Path | ||
import kotlin.io.path.exists | ||
import kotlin.io.path.relativeTo | ||
|
||
// todo: this is disabled for now, it's a process under ReadAction, and needs to be run in the indexing phase | ||
class AptosBytecodeDecompiler: BinaryFileDecompiler { | ||
override fun decompile(file: VirtualFile): CharSequence { | ||
val fileText = file.readText() | ||
try { | ||
StringUtil.assertValidSeparators(fileText) | ||
return fileText | ||
} catch (e: AssertionError) { | ||
val bytes = file.readBytes() | ||
return LoadTextUtil.getTextByBinaryPresentation(bytes, file) | ||
} | ||
// val project = | ||
// ProjectLocator.getInstance().getProjectsForFile(file).firstOrNull { it?.isAptosConfigured == true } | ||
// ?: ProjectManager.getInstance().defaultProject.takeIf { it.isAptosConfigured } | ||
// ?: return file.readText() | ||
// val targetFileDir = getDecompilerTargetFileDirOnTemp(project, file) ?: return file.readText() | ||
// val targetFile = decompileFile(project, file, targetFileDir) ?: return file.readText() | ||
// return LoadTextUtil.loadText(targetFile) | ||
} | ||
|
||
fun decompileFileToTheSameDir(project: Project, file: VirtualFile): RsResult<VirtualFile, String> { | ||
val disposable = project.createDisposableOnFileChange(file) | ||
val aptos = project.getAptosCli(disposable) ?: return RsResult.Err("No Aptos CLI configured") | ||
|
||
aptos.decompileFile(file.path, outputDir = null) | ||
.unwrapOrElse { | ||
return RsResult.Err("`aptos move decompile` failed") | ||
} | ||
val decompiledFilePath = file.parent.pathAsPath.resolve(sourceFileName(file)) | ||
val decompiledFile = VirtualFileManager.getInstance().refreshAndFindFileByNioPath(decompiledFilePath) | ||
?: run { | ||
// something went wrong, no output file | ||
return RsResult.Err("Expected decompiled file $decompiledFilePath does not exist") | ||
} | ||
return RsResult.Ok(decompiledFile) | ||
} | ||
|
||
fun decompileFile(project: Project, file: VirtualFile, targetFileDir: Path): RsResult<VirtualFile, String> { | ||
val disposable = project.createDisposableOnFileChange(file) | ||
val aptos = project.getAptosCli(disposable) ?: return RsResult.Err("No Aptos CLI configured") | ||
|
||
if (!targetFileDir.exists()) { | ||
targetFileDir.toFile().mkdirs() | ||
} | ||
|
||
aptos.decompileFile(file.path, outputDir = targetFileDir.toString()) | ||
.unwrapOrElse { | ||
return RsResult.Err("`aptos move decompile` failed") | ||
} | ||
val decompiledName = sourceFileName(file) | ||
val decompiledFile = | ||
VirtualFileManager.getInstance().findFileByNioPath(targetFileDir.resolve(decompiledName)) ?: run { | ||
// something went wrong, no output file | ||
return RsResult.Err("Cannot find decompiled file in the target directory") | ||
} | ||
|
||
val decompiledNioFile = decompiledFile.toNioPathOrNull()?.toFile() | ||
?: return RsResult.Err("Cannot convert VirtualFile to File") | ||
FileUtil.rename(decompiledNioFile, hashedSourceFileName(file)) | ||
|
||
return RsResult.Ok(decompiledFile) | ||
} | ||
|
||
fun getDecompilerTargetFileDirOnTemp(project: Project, file: VirtualFile): Path? { | ||
val rootDecompilerDir = getArtifactsDir() | ||
val projectDecompilerDir = rootDecompilerDir.resolve(project.name) | ||
val root = project.rootPath ?: return null | ||
val relativeFilePath = file.parent.pathAsPath.relativeTo(root) | ||
val targetFileDir = projectDecompilerDir.toPath().resolve(relativeFilePath) | ||
return targetFileDir | ||
} | ||
|
||
fun getArtifactsDir(): File { | ||
return File(FileUtil.getTempDirectory(), "intellij-move-decompiled-artifacts") | ||
} | ||
|
||
fun sourceFileName(file: VirtualFile): String { | ||
val fileName = file.name | ||
return "$fileName.move" | ||
} | ||
|
||
fun hashedSourceFileName(file: VirtualFile): String { | ||
val fileName = file.name | ||
return "$fileName#decompiled.move" | ||
} | ||
} | ||
|
||
fun Project.createDisposableOnFileChange(file: VirtualFile): Disposable { | ||
val filePath = file.path | ||
val disposable = Disposer.newDisposable("Dispose on any change to the ${file.name} file") | ||
messageBus.connect(disposable).subscribe( | ||
VirtualFileManager.VFS_CHANGES, | ||
object: BulkFileListener { | ||
override fun after(events: MutableList<out VFileEvent>) { | ||
if (events.any { it.path == filePath }) { | ||
Disposer.dispose(disposable) | ||
} | ||
} | ||
} | ||
) | ||
Disposer.register(this.rootDisposable, disposable) | ||
return disposable | ||
} |
37 changes: 37 additions & 0 deletions
37
src/main/kotlin/org/move/bytecode/DecompileAptosMvFileAction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package org.move.bytecode | ||
|
||
import com.intellij.notification.NotificationType.ERROR | ||
import com.intellij.openapi.actionSystem.AnActionEvent | ||
import com.intellij.openapi.actionSystem.CommonDataKeys | ||
import com.intellij.openapi.project.DumbAwareAction | ||
import org.move.bytecode.AptosBytecodeNotificationProvider.DecompilationModalTask | ||
import org.move.cli.settings.getAptosCli | ||
import org.move.ide.MoveIcons | ||
import org.move.ide.notifications.showBalloon | ||
import org.move.openapiext.openFile | ||
import org.move.stdext.unwrapOrElse | ||
import java.util.* | ||
|
||
class DecompileAptosMvFileAction: DumbAwareAction("Decompile .mv File", null, MoveIcons.APTOS_LOGO) { | ||
override fun actionPerformed(e: AnActionEvent) { | ||
val project = e.project ?: return | ||
val file = e.getData(CommonDataKeys.PSI_FILE)?.virtualFile ?: return | ||
val decompiledFile = DecompilationModalTask(project, file).runWithProgress() | ||
.unwrapOrElse { | ||
project.showBalloon("Error with decompilation process", it, ERROR) | ||
return | ||
} | ||
project.openFile(decompiledFile) | ||
} | ||
|
||
override fun update(e: AnActionEvent) { | ||
val file = e.getData(CommonDataKeys.PSI_FILE) | ||
val presentation = e.presentation | ||
val enabled = | ||
(file != null | ||
&& Objects.nonNull(file.virtualFile) && !(file.virtualFile.fileSystem.isReadOnly) | ||
&& file.fileType == AptosBytecodeFileType | ||
&& e.getData(CommonDataKeys.PROJECT)?.getAptosCli() != null) | ||
presentation.isEnabledAndVisible = enabled | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/main/kotlin/org/move/bytecode/FetchAptosPackageAction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package org.move.bytecode | ||
|
||
import com.intellij.openapi.actionSystem.AnActionEvent | ||
import org.move.cli.runConfigurations.aptos.RunAptosCommandActionBase | ||
|
||
class FetchAptosPackageAction: RunAptosCommandActionBase("Fetch on-chain package") { | ||
override fun actionPerformed(e: AnActionEvent) { | ||
val project = e.project ?: return | ||
|
||
val parametersDialog = FetchAptosPackageDialog(project) | ||
parametersDialog.show() | ||
} | ||
|
||
} |
Oops, something went wrong.