Skip to content

Bytecode command #207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion application/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.time.Instant

buildscript {
dependencies {
classpath 'org.xerial:sqlite-jdbc:3.39.2.0'
Expand Down Expand Up @@ -28,7 +30,7 @@ jib {
}
container {
mainClass = 'org.togetherjava.tjbot.BootstrapLauncher'
setCreationTime(java.time.Instant.now().toString())
setCreationTime(Instant.now().toString())
}
}

Expand All @@ -43,6 +45,7 @@ dependencies {
implementation 'org.jetbrains:annotations:23.0.0'

implementation project(':database')
implementation project(':inmemorycompiler')
implementation project(':utils')
implementation project(':formatter')

Expand Down Expand Up @@ -82,4 +85,14 @@ dependencies {

application {
mainClass = 'org.togetherjava.tjbot.BootstrapLauncher'

applicationDefaultJvmArgs = [
'--add-opens=jdk.jdeps/com.sun.tools.javap=ALL-UNNAMED'
]
}

run {
jvmArgs = [
'--add-opens=jdk.jdeps/com.sun.tools.javap=ALL-UNNAMED'
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.WolframAlphaCommand;
import org.togetherjava.tjbot.commands.mediaonly.MediaOnlyChannelListener;
import org.togetherjava.tjbot.commands.moderation.*;
import org.togetherjava.tjbot.commands.moderation.ReportCommand;
import org.togetherjava.tjbot.commands.moderation.attachment.BlacklistedAttachmentListener;
import org.togetherjava.tjbot.commands.moderation.modmail.ModMailCommand;
import org.togetherjava.tjbot.commands.moderation.scam.ScamBlocker;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.togetherjava.tjbot.commands.code;

import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;

import org.togetherjava.tjbot.commands.utils.CodeFence;
import org.togetherjava.tjbot.imc.CompilationResult;
import org.togetherjava.tjbot.imc.InMemoryCompiler;
import org.togetherjava.tjbot.imc.JavacOption;
import org.togetherjava.tjbot.javap.Javap;
import org.togetherjava.tjbot.javap.JavapOption;

import javax.annotation.Nullable;

import java.util.StringJoiner;

// TODO Javadoc
final class BytecodeAction implements CodeAction {
@Override
public String getLabel() {
return "Bytecode";
}

@Override
public MessageEmbed apply(CodeFence codeFence) {
if (!isLanguageSupported(codeFence.language())) {
return createResponse("Sorry, only Java is supported.");
}

String result = compile(codeFence.code());
return createResponse(result);
}

private static boolean isLanguageSupported(@Nullable String language) {
// Assume that absence indicates Java code
return language == null || "java".equals(language);
}

private static MessageEmbed createResponse(String content) {
// Rust highlighting looks decent for bytecode
CodeFence resultCodeFence = new CodeFence("rust", content);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe extract "rust" to a field?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure


return new EmbedBuilder().setTitle("Bytecode")
.setDescription(resultCodeFence.toMarkdown())
.setColor(CodeMessageHandler.AMBIENT_COLOR)
.build();
}

private static String compile(String code) {
// Raw compilation
CompilationResult compilationResult;
try {
compilationResult = InMemoryCompiler.compile(code, JavacOption.DEBUG_ALL);
} catch (RuntimeException e) {
// TODO logging
return "A fatal error has occurred during compilation: " + e.getMessage();
}

if (!compilationResult.success()) {
StringJoiner failureMessage = new StringJoiner("\n", "Compilation failed.\n", "");
compilationResult.compileInfos()
.forEach(info -> failureMessage.add(info.diagnostic().toString()));
return failureMessage.toString();
}

// Disassembling
String disassembledResult;
try {
disassembledResult = Javap.disassemble(compilationResult.bytes(), JavapOption.VERBOSE);
} catch (RuntimeException e) {
// TODO logging
return "A fatal error has occurred during disassembly: " + e.getMessage();
}

// TODO max length of embeds
return disassembledResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public CodeMessageHandler() {

componentIdInteractor = new ComponentIdInteractor(getInteractionType(), getName());

List<CodeAction> codeActions = List.of(new FormatCodeCommand());
List<CodeAction> codeActions = List.of(new FormatCodeAction(), new BytecodeAction());

labelToCodeAction = codeActions.stream()
.collect(Collectors.toMap(CodeAction::getLabel, Function.identity(), (x, y) -> y,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* <p>
* While it will attempt formatting for any language, best results are achieved for Java code.
*/
final class FormatCodeCommand implements CodeAction {
final class FormatCodeAction implements CodeAction {
private final Formatter formatter = new Formatter();

@Override
Expand Down
5 changes: 0 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ subprojects {
excludes {
// Disables "Track uses of "TO-DO" tags" rule.
message 'java:S1135'

// Disables "Regular expressions should not overflow the stack" rule.
// https://sonarcloud.io/project/issues?id=Together-Java_TJ-Bot&issues=AXvleoajNC7zjjhmSufQ&open=AXvleoajNC7zjjhmSufQ
// https://sonarcloud.io/project/issues?id=Together-Java_TJ-Bot&issues=AXvleoX5NC7zjjhmSufP&open=AXvleoX5NC7zjjhmSufP
message 'java:S5998'
}
}

Expand Down
1 change: 0 additions & 1 deletion formatter/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
}

9 changes: 9 additions & 0 deletions inmemorycompiler/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id 'java'
}

dependencies {
implementation 'com.google.code.findbugs:jsr305:3.0.2'

implementation project(':utils')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.togetherjava.tjbot.imc;

import java.util.LinkedHashMap;
import java.util.Map;

class ByteJavaFileObjectLoader extends ClassLoader {
private final Map<String, InMemoryByteJavaFileObject> nameToClassJFO = new LinkedHashMap<>();

public ByteJavaFileObjectLoader(ClassLoader parent) {
super(parent);
}

public InMemoryByteJavaFileObject registerJFO(InMemoryByteJavaFileObject jfo) {
nameToClassJFO.put(jfo.getName(), jfo);

return jfo;
}

/**
* Only meant to be used for registering and getting, not finding. There are no plans to
* implement this, and it is also not needed.
*
* @throws UnsupportedOperationException always
*/
@Override
protected Class<?> findClass(String name) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}

public byte[] getLastBytes() {
return last(nameToClassJFO.values().toArray(InMemoryByteJavaFileObject[]::new)).getBytes();
}

private <E> E last(E[] col) {
return col[col.length - 1];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.togetherjava.tjbot.imc;

import java.util.Arrays;
import java.util.Collections;

/**
* Class representing a compilation result of {@link InMemoryCompiler}
*/
public record CompilationResult(boolean success, byte[] bytes, Iterable<CompileInfo> compileInfos) {

private static final CompilationResult EMPTY_RESULT =
new CompilationResult(false, new byte[0], Collections.emptyList());

/**
* @return an empty compilation result
*/
public static CompilationResult empty() {
return EMPTY_RESULT;
}

/**
* Creates an unsuccessful compilation result
*
* @param compileInfos compilation infos
* @return the generated compilation result
*/
public static CompilationResult fail(Iterable<CompileInfo> compileInfos) {
return new CompilationResult(false, new byte[0], compileInfos);
}

/**
* Creates a successful compilation result
*
* @param bytes classfile bytecode
* @param compileInfos compilation infos
* @return the generated compilation result
*/
public static CompilationResult success(byte[] bytes, Iterable<CompileInfo> compileInfos) {
return new CompilationResult(true, bytes, compileInfos);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (!(o instanceof CompilationResult that) || success != that.success
|| !Arrays.equals(bytes, that.bytes)) {
return false;
}

return compileInfos.equals(that.compileInfos);
}

@Override
public int hashCode() {
int result = (success ? 1 : 0);

result = 31 * result + Arrays.hashCode(bytes);
result = 31 * result + compileInfos.hashCode();

return result;
}

@Override
public String toString() {
return "CompilationResult{" + "success=" + success + ", bytes=" + Arrays.toString(bytes)
+ ", compileInfos=" + compileInfos + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.togetherjava.tjbot.imc;

import javax.tools.Diagnostic;

import java.util.Objects;

/**
* Wrapper class for a {@link Diagnostic} of unknown type
*
* @see javax.tools.Diagnostic
*/
public record CompileInfo(Diagnostic<?> diagnostic) {
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof CompileInfo that))
return false;
return diagnostic.equals(that.diagnostic);
}

@Override
public int hashCode() {
return Objects.hash(diagnostic);
}

@Override
public String toString() {
return "CompileInfo{" + "diagnostic=" + diagnostic + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.togetherjava.tjbot.imc;

import javax.tools.SimpleJavaFileObject;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;

/**
* Represents a {@link SimpleJavaFileObject} that exists purely in memory.
*
* @see javax.tools.SimpleJavaFileObject
*/
class InMemoryByteJavaFileObject extends SimpleJavaFileObject {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();

public InMemoryByteJavaFileObject(String className) {
super(URI.create(className), Kind.CLASS);
}

@Override
public OutputStream openOutputStream() {
return baos;
}

public byte[] getBytes() {
return baos.toByteArray();
}
}
Loading