upload
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# ──── CMake ────
|
||||
build/
|
||||
bin/
|
||||
cmake-build-*/
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
@@ -45,3 +46,6 @@ build
|
||||
|
||||
# Ignore Kotlin plugin data
|
||||
.kotlin
|
||||
|
||||
|
||||
.claude
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(torchcs_mc)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
add_subdirectory(src/bedrock)
|
||||
add_subdirectory(src/torchcs/core)
|
||||
add_subdirectory(src/torchcs/core)
|
||||
add_subdirectory(src/fernflower)
|
||||
add_subdirectory(src/ripper)
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import torchcs.tasks.SetupMinecraft
|
||||
|
||||
|
||||
layout.buildDirectory = file('build/gradle')
|
||||
|
||||
subprojects {
|
||||
|
||||
@@ -5,15 +5,15 @@ import java.io.IOException;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.Directory;
|
||||
import org.gradle.api.file.ProjectLayout;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import torchcs.tasks.common.HttpClientMinecraftApi;
|
||||
import torchcs.tasks.common.ProjectPath;
|
||||
|
||||
import torchcs.tasks.common.ripper.Ripper;
|
||||
|
||||
public abstract class SetupMinecraft extends DefaultTask {
|
||||
|
||||
@@ -24,7 +24,10 @@ public abstract class SetupMinecraft extends DefaultTask {
|
||||
@TaskAction
|
||||
public void run() throws IOException, InterruptedException {
|
||||
init();
|
||||
setupMinecraftJava();
|
||||
}
|
||||
|
||||
private void setupMinecraftJava() throws IOException, InterruptedException {
|
||||
JsonObject metadata = HttpClientMinecraftApi.fetchLatestJavaServerMetadata();
|
||||
|
||||
if (metadata == null) {
|
||||
@@ -32,8 +35,28 @@ public abstract class SetupMinecraft extends DefaultTask {
|
||||
return;
|
||||
}
|
||||
|
||||
String pretty = new GsonBuilder().setPrettyPrinting().create().toJson(metadata);
|
||||
getLogger().lifecycle(pretty);
|
||||
String downloadUrl = metadata.get("downloads").getAsJsonObject().get("server").getAsJsonObject().get("url").getAsString();
|
||||
String versionId = metadata.get("id").getAsString();
|
||||
Directory versionPath = projectPath.getJavaPath().dir(versionId);
|
||||
|
||||
if(versionPath.getAsFile().exists()) {
|
||||
getLogger().lifecycle("Minecraft Java {} is already set up, skipping.", versionId);
|
||||
return;
|
||||
}
|
||||
|
||||
versionPath.getAsFile().mkdirs();
|
||||
|
||||
getLogger().lifecycle("Downloading Minecraft Java server {}...", versionId);
|
||||
|
||||
HttpClientMinecraftApi.downloadFile(downloadUrl, versionPath);
|
||||
|
||||
getLogger().lifecycle("Download complete.");
|
||||
getLogger().lifecycle("Decompiling server jar...");
|
||||
|
||||
Ripper.decompile(versionPath.file("server.jar").getAsFile(), versionPath.dir("src").getAsFile());
|
||||
|
||||
getLogger().lifecycle("Decompilation complete.");
|
||||
getLogger().lifecycle("Setup finished successfully.");
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package torchcs.tasks.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import org.gradle.api.file.Directory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -42,6 +48,20 @@ public class HttpClientMinecraftApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void downloadFile(String url, Directory outPath) throws IOException, InterruptedException {
|
||||
String fileName = url.substring(url.lastIndexOf('/') + 1);
|
||||
Path destination = outPath.getAsFile().toPath().resolve(fileName);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("User-Agent", "TorchCS/1.0")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
Files.copy(response.body(), destination, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
public static JsonObject fetch(String url) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
|
||||
@@ -19,18 +19,40 @@ public class ProjectPath {
|
||||
return getBinPath().dir("minecraft");
|
||||
}
|
||||
|
||||
public Directory getJavaPath() {
|
||||
return getMinecraftPath().dir("java");
|
||||
}
|
||||
|
||||
public Directory getBedrockPath() {
|
||||
return getMinecraftPath().dir("bedrock");
|
||||
}
|
||||
|
||||
public void initDirectories() {
|
||||
|
||||
Directory binPath = getBinPath();
|
||||
Directory path;
|
||||
|
||||
if (!binPath.getAsFile().exists()) {
|
||||
binPath.getAsFile().mkdirs();
|
||||
path = getBinPath();
|
||||
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
|
||||
Directory minecraftPath = getMinecraftPath();
|
||||
path = getMinecraftPath();
|
||||
|
||||
if (!minecraftPath.getAsFile().exists()) {
|
||||
minecraftPath.getAsFile().mkdirs();
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
|
||||
path = getJavaPath();
|
||||
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
|
||||
path = getBedrockPath();
|
||||
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
package torchcs.tasks.common.ripper;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
|
||||
|
||||
public class ConsoleLogger extends IFernflowerLogger {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ConsoleLogger.class.getName());
|
||||
|
||||
@Override
|
||||
public void writeMessage(String message, Severity severity) {
|
||||
logger.log(toLevel(severity), message);
|
||||
String line = "[Ripper/" + severity.name() + "] " + message;
|
||||
if (severity == Severity.ERROR) {
|
||||
System.err.println(line);
|
||||
} else {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMessage(String message, Severity severity, Throwable t) {
|
||||
logger.log(toLevel(severity), message, t);
|
||||
}
|
||||
|
||||
private static Level toLevel(Severity severity) {
|
||||
return switch (severity) {
|
||||
case TRACE -> Level.FINEST;
|
||||
case INFO -> Level.INFO;
|
||||
case WARN -> Level.WARNING;
|
||||
case ERROR -> Level.SEVERE;
|
||||
};
|
||||
writeMessage(message + " — " + t.getMessage(), severity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public class DirectoryResultSaver implements IResultSaver {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.out.println("[Ripper/INFO] Written: " + entryName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,6 +43,7 @@ public class DirectoryResultSaver implements IResultSaver {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.out.println("[Ripper/INFO] Written: " + entryName);
|
||||
}
|
||||
|
||||
@Override public void saveDirEntry(String path, String archiveName, String entryName) {}
|
||||
|
||||
@@ -1,20 +1,58 @@
|
||||
package torchcs.tasks.common.ripper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler;
|
||||
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider;
|
||||
|
||||
public class Ripper {
|
||||
public static void decompile(File inputJar, File outputDir) {
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
IBytecodeProvider provider = (externalPath, internalPath) -> Files.readAllBytes(new File(externalPath).toPath());
|
||||
BaseDecompiler decompiler = new BaseDecompiler(provider, new DirectoryResultSaver(outputDir), options, new ConsoleLogger());
|
||||
decompiler.addSource(inputJar);
|
||||
|
||||
public static void decompile(File inputJar, File outputDir) throws IOException {
|
||||
File targetJar = extractInnerServerJar(inputJar);
|
||||
|
||||
IBytecodeProvider provider = (externalPath, internalPath) -> {
|
||||
if (internalPath == null) {
|
||||
return Files.readAllBytes(new File(externalPath).toPath());
|
||||
}
|
||||
try (JarFile jar = new JarFile(externalPath)) {
|
||||
JarEntry entry = jar.getJarEntry(internalPath);
|
||||
try (InputStream is = jar.getInputStream(entry)) {
|
||||
return is.readAllBytes();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BaseDecompiler decompiler = new BaseDecompiler(provider, new DirectoryResultSaver(outputDir), Map.of("thr", String.valueOf(Runtime.getRuntime().availableProcessors())), new ConsoleLogger());
|
||||
decompiler.addSource(targetJar);
|
||||
decompiler.decompileContext();
|
||||
|
||||
if (!targetJar.equals(inputJar)) {
|
||||
targetJar.delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private static File extractInnerServerJar(File bundlerJar) throws IOException {
|
||||
try (JarFile jar = new JarFile(bundlerJar)) {
|
||||
Optional<JarEntry> serverEntry = jar.stream()
|
||||
.filter(e -> e.getName().startsWith("META-INF/versions/") && e.getName().endsWith(".jar"))
|
||||
.findFirst();
|
||||
|
||||
if (serverEntry.isPresent()) {
|
||||
File extracted = new File(bundlerJar.getParentFile(), "server-inner.jar");
|
||||
try (InputStream is = jar.getInputStream(serverEntry.get())) {
|
||||
Files.copy(is, extracted.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return extracted;
|
||||
}
|
||||
}
|
||||
return bundlerJar;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "AnnotationExprent.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
AnnotationExprent::AnnotationExprent(std::string className,
|
||||
std::vector<std::string> parNames,
|
||||
std::vector<Exprent*> parValues)
|
||||
: Exprent(EXPRENT_ANNOTATION)
|
||||
, className_(std::move(className))
|
||||
, parNames_(std::move(parNames))
|
||||
, parValues_(std::move(parValues))
|
||||
{}
|
||||
|
||||
std::vector<Exprent*> AnnotationExprent::getAllExprents() const {
|
||||
return parValues_;
|
||||
}
|
||||
|
||||
int AnnotationExprent::getAnnotationType() const {
|
||||
if (parNames_.empty()) return ANNOTATION_MARKER;
|
||||
if (parNames_.size() == 1 && parNames_[0] == "value") return ANNOTATION_SINGLE_ELEMENT;
|
||||
return ANNOTATION_NORMAL;
|
||||
}
|
||||
|
||||
TextBuffer* AnnotationExprent::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
auto* buffer = new TextBuffer();
|
||||
buffer->append('@');
|
||||
buffer->append(className_);
|
||||
|
||||
int type = getAnnotationType();
|
||||
if (type != ANNOTATION_MARKER) {
|
||||
buffer->append('(');
|
||||
bool oneLiner = (type == ANNOTATION_SINGLE_ELEMENT || indent < 0);
|
||||
for (size_t i = 0; i < parNames_.size(); ++i) {
|
||||
if (!oneLiner) {
|
||||
buffer->append('\n');
|
||||
for (int j = 0; j < indent + 1; ++j) buffer->append(" ");
|
||||
}
|
||||
if (type != ANNOTATION_SINGLE_ELEMENT) {
|
||||
buffer->append(parNames_[i]);
|
||||
buffer->append(" = ");
|
||||
}
|
||||
if (i < parValues_.size() && parValues_[i]) {
|
||||
TextBuffer* vb = parValues_[i]->toJava(0, tracer);
|
||||
buffer->append(vb->toString());
|
||||
delete vb;
|
||||
}
|
||||
if (i + 1 < parNames_.size()) buffer->append(',');
|
||||
}
|
||||
if (!oneLiner) {
|
||||
buffer->append('\n');
|
||||
for (int j = 0; j < indent; ++j) buffer->append(" ");
|
||||
}
|
||||
buffer->append(')');
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool AnnotationExprent::equals(const Exprent* o) const {
|
||||
if (o == this) return true;
|
||||
auto* ann = dynamic_cast<const AnnotationExprent*>(o);
|
||||
if (!ann) return false;
|
||||
return className_ == ann->className_ && parNames_ == ann->parNames_;
|
||||
}
|
||||
|
||||
void AnnotationExprent::fillBytecodeRange(uint64_t* values) const {
|
||||
measureBytecode(values, parValues_);
|
||||
measureBytecode(values);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Exprent.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class AnnotationExprent : public Exprent {
|
||||
public:
|
||||
static constexpr int ANNOTATION_NORMAL = 1;
|
||||
static constexpr int ANNOTATION_MARKER = 2;
|
||||
static constexpr int ANNOTATION_SINGLE_ELEMENT = 3;
|
||||
|
||||
AnnotationExprent(std::string className,
|
||||
std::vector<std::string> parNames,
|
||||
std::vector<Exprent*> parValues);
|
||||
|
||||
std::vector<Exprent*> getAllExprents() const override;
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
void fillBytecodeRange(uint64_t* values) const override;
|
||||
|
||||
bool equals(const Exprent* o) const;
|
||||
|
||||
const std::string& getClassName() const { return className_; }
|
||||
int getAnnotationType() const;
|
||||
|
||||
private:
|
||||
std::string className_;
|
||||
std::vector<std::string> parNames_;
|
||||
std::vector<Exprent*> parValues_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ArrayExprent.h"
|
||||
#include "CheckTypesResult.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
ArrayExprent::ArrayExprent(Exprent* array, Exprent* index, VarType hardType, uint64_t bytecodeOffsets)
|
||||
: Exprent(EXPRENT_ARRAY)
|
||||
, array_(array)
|
||||
, index_(index)
|
||||
, hardType_(std::move(hardType))
|
||||
{
|
||||
bytecode = bytecodeOffsets;
|
||||
}
|
||||
|
||||
VarType ArrayExprent::getExprType() const {
|
||||
VarType exprType = array_->getExprType();
|
||||
if (exprType == VarType::VARTYPE_NULL) {
|
||||
return hardType_.copy();
|
||||
}
|
||||
return exprType.decreaseArrayDim();
|
||||
}
|
||||
|
||||
int ArrayExprent::getExprentUse() const {
|
||||
return array_->getExprentUse() & index_->getExprentUse() & Exprent::MULTIPLE_USES;
|
||||
}
|
||||
|
||||
CheckTypesResult* ArrayExprent::checkExprTypeBounds() {
|
||||
CheckTypesResult* result = new CheckTypesResult();
|
||||
result->addMinTypeExprent(index_, VarType::VARTYPE_BYTECHAR);
|
||||
result->addMaxTypeExprent(index_, VarType::VARTYPE_INT);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Exprent*> ArrayExprent::getAllExprents() const {
|
||||
return { array_, index_ };
|
||||
}
|
||||
|
||||
Exprent* ArrayExprent::copy() const {
|
||||
return new ArrayExprent(array_->copy(), index_->copy(), hardType_, bytecode);
|
||||
}
|
||||
|
||||
TextBuffer* ArrayExprent::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
TextBuffer* res = array_->toJava(indent, tracer);
|
||||
if (tracer) tracer->addMapping(bytecode);
|
||||
res->append('[');
|
||||
TextBuffer* idxBuf = index_->toJava(indent, tracer);
|
||||
res->append(idxBuf->toString());
|
||||
delete idxBuf;
|
||||
res->append(']');
|
||||
return res;
|
||||
}
|
||||
|
||||
void ArrayExprent::replaceExprent(Exprent* oldExpr, Exprent* newExpr) {
|
||||
if (oldExpr == array_) array_ = newExpr;
|
||||
if (oldExpr == index_) index_ = newExpr;
|
||||
}
|
||||
|
||||
bool ArrayExprent::equals(const Exprent* o) const {
|
||||
if (o == this) return true;
|
||||
auto* arr = dynamic_cast<const ArrayExprent*>(o);
|
||||
if (!arr) return false;
|
||||
return array_->equals(arr->array_) && index_->equals(arr->index_);
|
||||
}
|
||||
|
||||
void ArrayExprent::fillBytecodeRange(uint64_t* values) const {
|
||||
measureBytecode(values, array_);
|
||||
measureBytecode(values, index_);
|
||||
measureBytecode(values);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Exprent.h"
|
||||
#include "VarType.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
class CheckTypesResult;
|
||||
|
||||
class ArrayExprent : public Exprent {
|
||||
public:
|
||||
ArrayExprent(Exprent* array, Exprent* index, VarType hardType, uint64_t bytecodeOffsets);
|
||||
|
||||
VarType getExprType() const override;
|
||||
int getExprentUse() const override;
|
||||
|
||||
CheckTypesResult* checkExprTypeBounds();
|
||||
std::vector<Exprent*> getAllExprents() const override;
|
||||
Exprent* copy() const override;
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
void replaceExprent(Exprent* oldExpr, Exprent* newExpr) override;
|
||||
bool equals(const Exprent* o) const override;
|
||||
void fillBytecodeRange(uint64_t* values) const override;
|
||||
|
||||
Exprent* getArray() const { return array_; }
|
||||
Exprent* getIndex() const { return index_; }
|
||||
|
||||
private:
|
||||
Exprent* array_;
|
||||
Exprent* index_;
|
||||
VarType hardType_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "AssignmentExprent.h"
|
||||
#include "CheckTypesResult.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
const char* AssignmentExprent::OPERATORS[] = {
|
||||
" += ", // FUNCTION_ADD
|
||||
" -= ", // FUNCTION_SUB
|
||||
" *= ", // FUNCTION_MUL
|
||||
" /= ", // FUNCTION_DIV
|
||||
" &= ", // FUNCTION_AND
|
||||
" |= ", // FUNCTION_OR
|
||||
" ^= ", // FUNCTION_XOR
|
||||
" %= ", // FUNCTION_REM
|
||||
" <<= ", // FUNCTION_SHL
|
||||
" >>= ", // FUNCTION_SHR
|
||||
" >>>= " // FUNCTION_USHR
|
||||
};
|
||||
|
||||
AssignmentExprent::AssignmentExprent(Exprent* left, Exprent* right, uint64_t bytecodeOffsets)
|
||||
: Exprent(EXPRENT_ASSIGNMENT)
|
||||
, left_(left)
|
||||
, right_(right)
|
||||
{
|
||||
bytecode = bytecodeOffsets;
|
||||
}
|
||||
|
||||
VarType AssignmentExprent::getExprType() const {
|
||||
return left_->getExprType();
|
||||
}
|
||||
|
||||
void AssignmentExprent::inferExprType(const VarType& upperBound) {
|
||||
left_->inferExprType(upperBound);
|
||||
}
|
||||
|
||||
CheckTypesResult* AssignmentExprent::checkExprTypeBounds() {
|
||||
CheckTypesResult* result = new CheckTypesResult();
|
||||
|
||||
VarType typeLeft = left_->getExprType();
|
||||
VarType typeRight = right_->getExprType();
|
||||
|
||||
if (typeLeft.getTypeFamily() > typeRight.getTypeFamily()) {
|
||||
result->addMinTypeExprent(right_, VarType::getMinTypeInFamily(typeLeft.getTypeFamily()));
|
||||
} else if (typeLeft.getTypeFamily() < typeRight.getTypeFamily()) {
|
||||
result->addMinTypeExprent(left_, typeRight);
|
||||
} else {
|
||||
auto* sup = VarType::getCommonSupertype(typeLeft, typeRight);
|
||||
if (sup) result->addMinTypeExprent(left_, *sup);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Exprent*> AssignmentExprent::getAllExprents() const {
|
||||
return { left_, right_ };
|
||||
}
|
||||
|
||||
Exprent* AssignmentExprent::copy() const {
|
||||
return new AssignmentExprent(left_->copy(), right_->copy(), bytecode);
|
||||
}
|
||||
|
||||
TextBuffer* AssignmentExprent::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
auto* buffer = new TextBuffer();
|
||||
TextBuffer* leftBuf = left_->toJava(indent, tracer);
|
||||
buffer->append(leftBuf->toString());
|
||||
delete leftBuf;
|
||||
|
||||
if (condType_ == CONDITION_NONE) {
|
||||
buffer->append(" = ");
|
||||
} else {
|
||||
buffer->append(OPERATORS[condType_]);
|
||||
}
|
||||
|
||||
TextBuffer* rightBuf = right_->toJava(indent, tracer);
|
||||
buffer->append(rightBuf->toString());
|
||||
delete rightBuf;
|
||||
|
||||
if (tracer) tracer->addMapping(bytecode);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void AssignmentExprent::replaceExprent(Exprent* oldExpr, Exprent* newExpr) {
|
||||
if (oldExpr == left_) left_ = newExpr;
|
||||
if (oldExpr == right_) right_ = newExpr;
|
||||
}
|
||||
|
||||
bool AssignmentExprent::equals(const Exprent* o) const {
|
||||
if (o == this) return true;
|
||||
auto* as = dynamic_cast<const AssignmentExprent*>(o);
|
||||
if (!as) return false;
|
||||
return left_->equals(as->left_) && right_->equals(as->right_) && condType_ == as->condType_;
|
||||
}
|
||||
|
||||
void AssignmentExprent::fillBytecodeRange(uint64_t* values) const {
|
||||
measureBytecode(values, left_);
|
||||
measureBytecode(values, right_);
|
||||
measureBytecode(values);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Exprent.h"
|
||||
#include "VarType.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
class CheckTypesResult;
|
||||
|
||||
class AssignmentExprent : public Exprent {
|
||||
public:
|
||||
static constexpr int CONDITION_NONE = -1;
|
||||
|
||||
static const char* OPERATORS[]; // indexed by condition type
|
||||
|
||||
AssignmentExprent(Exprent* left, Exprent* right, uint64_t bytecodeOffsets);
|
||||
|
||||
VarType getExprType() const override;
|
||||
void inferExprType(const VarType& upperBound) override;
|
||||
|
||||
CheckTypesResult* checkExprTypeBounds();
|
||||
std::vector<Exprent*> getAllExprents() const override;
|
||||
Exprent* copy() const override;
|
||||
int getPrecedence() const override { return 13; }
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
void replaceExprent(Exprent* oldExpr, Exprent* newExpr) override;
|
||||
bool equals(const Exprent* o) const override;
|
||||
void fillBytecodeRange(uint64_t* values) const override;
|
||||
|
||||
Exprent* getLeft() const { return left_; }
|
||||
Exprent* getRight() const { return right_; }
|
||||
void setRight(Exprent* r) { right_ = r; }
|
||||
int getCondType() const { return condType_; }
|
||||
void setCondType(int ct) { condType_ = ct; }
|
||||
|
||||
private:
|
||||
Exprent* left_;
|
||||
Exprent* right_;
|
||||
int condType_ = CONDITION_NONE;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "BaseDecompiler.h"
|
||||
#include "Fernflower.h"
|
||||
#include "CancellationManager.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
BaseDecompiler::BaseDecompiler(IBytecodeProvider* provider,
|
||||
IResultSaver* saver,
|
||||
const std::unordered_map<std::string, std::string>* options,
|
||||
IFernflowerLogger* logger)
|
||||
: BaseDecompiler(provider, saver, options, logger, nullptr) {}
|
||||
|
||||
BaseDecompiler::BaseDecompiler(IBytecodeProvider* provider,
|
||||
IResultSaver* saver,
|
||||
const std::unordered_map<std::string, std::string>* options,
|
||||
IFernflowerLogger* logger,
|
||||
CancellationManager* cancellationManager)
|
||||
: engine_(std::make_unique<Fernflower>(provider, saver, options, logger, cancellationManager)) {}
|
||||
|
||||
BaseDecompiler::~BaseDecompiler() = default;
|
||||
|
||||
void BaseDecompiler::addSource(const std::string& source) {
|
||||
engine_->addSource(source);
|
||||
}
|
||||
|
||||
void BaseDecompiler::addLibrary(const std::string& library) {
|
||||
engine_->addLibrary(library);
|
||||
}
|
||||
|
||||
void BaseDecompiler::decompileContext() {
|
||||
try {
|
||||
engine_->decompileContext();
|
||||
} catch (...) {
|
||||
engine_->clearContext();
|
||||
throw;
|
||||
}
|
||||
engine_->clearContext();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "IBytecodeProvider.h"
|
||||
#include "IResultSaver.h"
|
||||
#include "IFernflowerLogger.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Fernflower;
|
||||
class CancellationManager;
|
||||
|
||||
class BaseDecompiler {
|
||||
public:
|
||||
BaseDecompiler(IBytecodeProvider* provider,
|
||||
IResultSaver* saver,
|
||||
const std::unordered_map<std::string, std::string>* options,
|
||||
IFernflowerLogger* logger);
|
||||
|
||||
BaseDecompiler(IBytecodeProvider* provider,
|
||||
IResultSaver* saver,
|
||||
const std::unordered_map<std::string, std::string>* options,
|
||||
IFernflowerLogger* logger,
|
||||
CancellationManager* cancellationManager);
|
||||
|
||||
~BaseDecompiler();
|
||||
|
||||
void addSource(const std::string& source);
|
||||
void addLibrary(const std::string& library);
|
||||
void decompileContext();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Fernflower> engine_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "BasicBlock.h"
|
||||
#include "Instruction.h"
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace ff {
|
||||
|
||||
BasicBlock::BasicBlock(int id)
|
||||
: id(id), seq_(std::make_unique<SimpleInstructionSequence>()) {}
|
||||
|
||||
BasicBlock::BasicBlock(int id, std::unique_ptr<InstructionSequence> seq)
|
||||
: id(id), seq_(std::move(seq)) {}
|
||||
|
||||
std::unique_ptr<BasicBlock> BasicBlock::clone(int newId) const {
|
||||
auto block = std::make_unique<BasicBlock>(newId, seq_->clone());
|
||||
block->originalOffsets_ = originalOffsets_;
|
||||
return block;
|
||||
}
|
||||
|
||||
Instruction* BasicBlock::getInstruction(int index) {
|
||||
return seq_->getInstr(index);
|
||||
}
|
||||
const Instruction* BasicBlock::getInstruction(int index) const {
|
||||
return seq_->getInstr(index);
|
||||
}
|
||||
Instruction* BasicBlock::getLastInstruction() {
|
||||
return seq_->isEmpty() ? nullptr : seq_->getLastInstr();
|
||||
}
|
||||
const Instruction* BasicBlock::getLastInstruction() const {
|
||||
return seq_->isEmpty() ? nullptr : seq_->getLastInstr();
|
||||
}
|
||||
|
||||
int BasicBlock::getOriginalOffset(int index) const {
|
||||
return (index < static_cast<int>(originalOffsets_.size())) ? originalOffsets_[index] : -1;
|
||||
}
|
||||
int BasicBlock::size() const { return seq_->length(); }
|
||||
|
||||
void BasicBlock::addPredecessor(BasicBlock* block) {
|
||||
predecessors_.push_back(block);
|
||||
}
|
||||
void BasicBlock::removePredecessor(BasicBlock* block) {
|
||||
auto& v = predecessors_;
|
||||
v.erase(std::remove(v.begin(), v.end(), block), v.end());
|
||||
}
|
||||
void BasicBlock::addSuccessor(BasicBlock* block) {
|
||||
successors_.push_back(block);
|
||||
block->addPredecessor(this);
|
||||
}
|
||||
void BasicBlock::removeSuccessor(BasicBlock* block) {
|
||||
auto& v = successors_;
|
||||
v.erase(std::remove(v.begin(), v.end(), block), v.end());
|
||||
block->removePredecessor(this);
|
||||
}
|
||||
void BasicBlock::replaceSuccessor(BasicBlock* oldBlock, BasicBlock* newBlock) {
|
||||
for (auto& s : successors_) {
|
||||
if (s->id == oldBlock->id) {
|
||||
s = newBlock;
|
||||
oldBlock->removePredecessor(this);
|
||||
newBlock->addPredecessor(this);
|
||||
}
|
||||
}
|
||||
for (auto& s : successorExceptions_) {
|
||||
if (s->id == oldBlock->id) {
|
||||
s = newBlock;
|
||||
oldBlock->removePredecessorException(this);
|
||||
newBlock->addPredecessorException(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BasicBlock::addPredecessorException(BasicBlock* block) {
|
||||
predecessorExceptions_.push_back(block);
|
||||
}
|
||||
void BasicBlock::removePredecessorException(BasicBlock* block) {
|
||||
auto& v = predecessorExceptions_;
|
||||
v.erase(std::remove(v.begin(), v.end(), block), v.end());
|
||||
}
|
||||
void BasicBlock::addSuccessorException(BasicBlock* block) {
|
||||
if (std::find(successorExceptions_.begin(), successorExceptions_.end(), block) == successorExceptions_.end()) {
|
||||
successorExceptions_.push_back(block);
|
||||
block->addPredecessorException(this);
|
||||
}
|
||||
}
|
||||
void BasicBlock::removeSuccessorException(BasicBlock* block) {
|
||||
auto& v = successorExceptions_;
|
||||
v.erase(std::remove(v.begin(), v.end(), block), v.end());
|
||||
block->removePredecessorException(this);
|
||||
}
|
||||
|
||||
bool BasicBlock::isSuccessor(BasicBlock* block) const {
|
||||
return std::any_of(successors_.begin(), successors_.end(),
|
||||
[&](BasicBlock* s){ return s->id == block->id; });
|
||||
}
|
||||
|
||||
std::vector<IGraphNode*> BasicBlock::getPredecessorNodes() {
|
||||
std::vector<IGraphNode*> lst;
|
||||
lst.reserve(predecessors_.size() + predecessorExceptions_.size());
|
||||
for (auto* p : predecessors_) lst.push_back(p);
|
||||
for (auto* p : predecessorExceptions_) lst.push_back(p);
|
||||
return lst;
|
||||
}
|
||||
|
||||
int BasicBlock::getStartInstruction() const {
|
||||
return seq_->isEmpty() ? 0 : originalOffsets_[0];
|
||||
}
|
||||
int BasicBlock::getEndInstruction() const {
|
||||
if (seq_->isEmpty()) return 0;
|
||||
int end = seq_->getLastInstr()->length;
|
||||
return end + originalOffsets_[size() - 1];
|
||||
}
|
||||
|
||||
std::string BasicBlock::toString() const {
|
||||
return std::to_string(id) + ":\n" + seq_->toString(0);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "IGraphNode.h"
|
||||
#include "SimpleInstructionSequence.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class BasicBlock : public IGraphNode {
|
||||
public:
|
||||
const int id;
|
||||
int mark = 0;
|
||||
|
||||
explicit BasicBlock(int id);
|
||||
BasicBlock(int id, std::unique_ptr<InstructionSequence> seq);
|
||||
|
||||
std::unique_ptr<BasicBlock> clone(int newId) const;
|
||||
|
||||
Instruction* getInstruction(int index);
|
||||
const Instruction* getInstruction(int index) const;
|
||||
Instruction* getLastInstruction();
|
||||
const Instruction* getLastInstruction() const;
|
||||
|
||||
int getOriginalOffset(int index) const;
|
||||
int size() const;
|
||||
|
||||
void addPredecessor(BasicBlock* block);
|
||||
void removePredecessor(BasicBlock* block);
|
||||
void addSuccessor(BasicBlock* block);
|
||||
void removeSuccessor(BasicBlock* block);
|
||||
void replaceSuccessor(BasicBlock* oldBlock, BasicBlock* newBlock);
|
||||
|
||||
void addPredecessorException(BasicBlock* block);
|
||||
void removePredecessorException(BasicBlock* block);
|
||||
void addSuccessorException(BasicBlock* block);
|
||||
void removeSuccessorException(BasicBlock* block);
|
||||
|
||||
bool isSuccessor(BasicBlock* block) const;
|
||||
|
||||
std::vector<int>& getOriginalOffsets() { return originalOffsets_; }
|
||||
const std::vector<int>& getOriginalOffsets() const { return originalOffsets_; }
|
||||
InstructionSequence* getSeq() { return seq_.get(); }
|
||||
const InstructionSequence* getSeq() const { return seq_.get(); }
|
||||
|
||||
std::vector<BasicBlock*>& getPredecessors() { return predecessors_; }
|
||||
const std::vector<BasicBlock*>& getPredecessors() const { return predecessors_; }
|
||||
std::vector<BasicBlock*>& getSuccessors() { return successors_; }
|
||||
const std::vector<BasicBlock*>& getSuccessors() const { return successors_; }
|
||||
std::vector<BasicBlock*>& getPredecessorExceptions() { return predecessorExceptions_; }
|
||||
const std::vector<BasicBlock*>& getPredecessorExceptions() const { return predecessorExceptions_; }
|
||||
std::vector<BasicBlock*>& getSuccessorExceptions() { return successorExceptions_; }
|
||||
const std::vector<BasicBlock*>& getSuccessorExceptions() const { return successorExceptions_; }
|
||||
|
||||
// IGraphNode
|
||||
std::vector<IGraphNode*> getPredecessorNodes() override;
|
||||
|
||||
int getStartInstruction() const;
|
||||
int getEndInstruction() const;
|
||||
std::string toString() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<InstructionSequence> seq_;
|
||||
std::vector<int> originalOffsets_;
|
||||
std::vector<BasicBlock*> predecessors_;
|
||||
std::vector<BasicBlock*> successors_;
|
||||
std::vector<BasicBlock*> predecessorExceptions_;
|
||||
std::vector<BasicBlock*> successorExceptions_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "BasicBlockStatement.h"
|
||||
#include "BasicBlock.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "CounterContainer.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "Exprent.h"
|
||||
#include "Instruction.h"
|
||||
#include "InstructionSequence.h"
|
||||
#include "SimpleInstructionSequence.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "TextUtil.h"
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
BasicBlockStatement::BasicBlockStatement(BasicBlock* block)
|
||||
: Statement(StatementType::BASIC_BLOCK, block->id)
|
||||
, block_(block) {
|
||||
|
||||
CounterContainer* container = DecompilerContext::getCounterContainer();
|
||||
if (id >= container->getCounter(CounterContainer::STATEMENT_COUNTER)) {
|
||||
container->setCounter(CounterContainer::STATEMENT_COUNTER, id + 1);
|
||||
}
|
||||
|
||||
Instruction* instr = block->getLastInstruction();
|
||||
if (instr) {
|
||||
if (instr->group == CodeConstants::GROUP_JUMP && instr->opcode != CodeConstants::opc_goto) {
|
||||
lastBasicType_ = StatementType::IF;
|
||||
} else if (instr->group == CodeConstants::GROUP_SWITCH) {
|
||||
lastBasicType_ = StatementType::SWITCH;
|
||||
}
|
||||
}
|
||||
|
||||
buildMonitorFlags();
|
||||
}
|
||||
|
||||
TextBuffer* BasicBlockStatement::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
auto* buf = new TextBuffer();
|
||||
|
||||
// Output var definitions
|
||||
for (auto& ep : varDefinitions_) {
|
||||
if (ep) {
|
||||
TextBuffer* t = ep->toJava(indent, tracer);
|
||||
buf->appendIndent(indent).append(*t).appendLineSeparator();
|
||||
delete t;
|
||||
if (tracer) tracer->incrementCurrentSourceLine();
|
||||
}
|
||||
}
|
||||
|
||||
// Output exprents
|
||||
for (auto& ep : exprents_) {
|
||||
if (ep) {
|
||||
TextBuffer* t = ep->toJava(indent, tracer);
|
||||
buf->appendIndent(indent).append(*t).append(";").appendLineSeparator();
|
||||
delete t;
|
||||
if (tracer) tracer->incrementCurrentSourceLine();
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
Statement* BasicBlockStatement::getSimpleCopy() {
|
||||
int newId = DecompilerContext::getCounterContainer()->getCounterAndIncrement(
|
||||
CounterContainer::STATEMENT_COUNTER);
|
||||
|
||||
auto seq = std::make_unique<SimpleInstructionSequence>();
|
||||
InstructionSequence* srcSeq = block_->getSeq();
|
||||
for (int i = 0; i < srcSeq->length(); i++) {
|
||||
seq->addInstruction(srcSeq->getInstr(i)->clone(), -1);
|
||||
}
|
||||
|
||||
auto* newBlock = new BasicBlock(newId, std::move(seq));
|
||||
return new BasicBlockStatement(newBlock);
|
||||
}
|
||||
|
||||
StartEndPair BasicBlockStatement::getStartEndRange() {
|
||||
if (block_->size() > 0) {
|
||||
return StartEndPair(block_->getStartInstruction(), block_->getEndInstruction());
|
||||
}
|
||||
return StartEndPair(0, 0);
|
||||
}
|
||||
|
||||
std::string BasicBlockStatement::toStringIndent(int indent) const {
|
||||
std::string buf = TextUtil::getIndentString(indent);
|
||||
buf += std::to_string((int)type);
|
||||
buf += ": ";
|
||||
buf += std::to_string(id);
|
||||
buf += DecompilerContext::getNewLineSeparator();
|
||||
// Append the sequence representation from the underlying BasicBlock
|
||||
if (block_ && block_->getSeq()) {
|
||||
buf += block_->getSeq()->toString();
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Statement.h"
|
||||
#include "StartEndPair.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
class BasicBlock;
|
||||
class BytecodeMappingTracer;
|
||||
class TextBuffer;
|
||||
|
||||
class BasicBlockStatement : public Statement {
|
||||
public:
|
||||
explicit BasicBlockStatement(BasicBlock* block);
|
||||
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
Statement* getSimpleCopy() override;
|
||||
|
||||
BasicBlock* getBlock() const { return block_; }
|
||||
|
||||
StartEndPair getStartEndRange();
|
||||
|
||||
protected:
|
||||
std::string toStringIndent(int indent) const;
|
||||
|
||||
private:
|
||||
BasicBlock* block_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "StructLineNumberTableAttribute.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
BytecodeMappingTracer BytecodeMappingTracer::DUMMY;
|
||||
|
||||
void BytecodeMappingTracer::addMapping(int bytecodeOffset) {
|
||||
mapping_.emplace(bytecodeOffset, currentSourceLine_);
|
||||
}
|
||||
|
||||
void BytecodeMappingTracer::addMapping(const std::vector<int>& bytecodeOffsets) {
|
||||
for (int off : bytecodeOffsets) addMapping(off);
|
||||
}
|
||||
|
||||
void BytecodeMappingTracer::addTracer(const BytecodeMappingTracer* tracer) {
|
||||
if (!tracer) return;
|
||||
for (const auto& [k, v] : tracer->mapping_) {
|
||||
mapping_.emplace(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<int,int> BytecodeMappingTracer::getOriginalLinesMapping() const {
|
||||
if (!lineNumberTable_) return {};
|
||||
|
||||
std::unordered_map<int,int> res;
|
||||
const auto& data = lineNumberTable_->getRawData();
|
||||
for (size_t i = 0; i + 1 < data.size(); i += 2) {
|
||||
int originalOffset = data[i];
|
||||
int originalLine = data[i + 1];
|
||||
auto it = mapping_.find(originalOffset);
|
||||
if (it != mapping_.end() && res.find(originalLine) == res.end()) {
|
||||
res[originalLine] = it->second;
|
||||
} else {
|
||||
const_cast<BytecodeMappingTracer*>(this)->unmappedLines_.insert(originalLine);
|
||||
}
|
||||
}
|
||||
for (const auto& [offset, newLine] : mapping_) {
|
||||
int originalLine = lineNumberTable_->findLineNumber(offset);
|
||||
if (originalLine > -1 && res.find(originalLine) == res.end()) {
|
||||
res[originalLine] = newLine;
|
||||
const_cast<BytecodeMappingTracer*>(this)->unmappedLines_.erase(originalLine);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class StructLineNumberTableAttribute;
|
||||
|
||||
class BytecodeMappingTracer {
|
||||
public:
|
||||
static BytecodeMappingTracer DUMMY;
|
||||
|
||||
BytecodeMappingTracer() = default;
|
||||
explicit BytecodeMappingTracer(int initialSourceLine) : currentSourceLine_(initialSourceLine) {}
|
||||
|
||||
void incrementCurrentSourceLine() { currentSourceLine_++; }
|
||||
void incrementCurrentSourceLine(int n) { currentSourceLine_ += n; }
|
||||
void setCurrentSourceLine(int line) { currentSourceLine_ = line; }
|
||||
int getCurrentSourceLine() const { return currentSourceLine_; }
|
||||
|
||||
void addMapping(int bytecodeOffset);
|
||||
void addMapping(const std::vector<int>& bytecodeOffsets);
|
||||
void addTracer(const BytecodeMappingTracer* tracer);
|
||||
|
||||
const std::unordered_map<int,int>& getMapping() const { return mapping_; }
|
||||
|
||||
void setLineNumberTable(StructLineNumberTableAttribute* lnt) { lineNumberTable_ = lnt; }
|
||||
|
||||
const std::unordered_set<int>& getUnmappedLines() const { return unmappedLines_; }
|
||||
|
||||
std::unordered_map<int,int> getOriginalLinesMapping() const;
|
||||
|
||||
private:
|
||||
int currentSourceLine_ = 0;
|
||||
StructLineNumberTableAttribute* lineNumberTable_ = nullptr;
|
||||
std::unordered_map<int,int> mapping_;
|
||||
std::unordered_set<int> unmappedLines_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "BytecodeSourceMapper.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "TextBuffer.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
void BytecodeSourceMapper::addMapping(const std::string& className, const std::string& methodName,
|
||||
int bytecodeOffset, int sourceLine) {
|
||||
mapping_[className][methodName].emplace(bytecodeOffset, sourceLine);
|
||||
}
|
||||
|
||||
void BytecodeSourceMapper::addTracer(const std::string& className, const std::string& methodName,
|
||||
const BytecodeMappingTracer& tracer) {
|
||||
for (const auto& [off, line] : tracer.getMapping()) {
|
||||
addMapping(className, methodName, off, line);
|
||||
}
|
||||
for (const auto& [orig, decomp] : tracer.getOriginalLinesMapping()) {
|
||||
linesMapping_.emplace(orig, decomp);
|
||||
}
|
||||
for (int ul : tracer.getUnmappedLines()) {
|
||||
unmappedLines_.insert(ul);
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeSourceMapper::dumpMapping(TextBuffer& buffer, bool offsetsToHex) const {
|
||||
if (mapping_.empty() && linesMapping_.empty()) return;
|
||||
|
||||
for (const auto& [className, classMap] : mapping_) {
|
||||
buffer.append("class '" + className + "' {\n");
|
||||
bool firstMethod = true;
|
||||
for (const auto& [methodName, methodMap] : classMap) {
|
||||
if (!firstMethod) buffer.append("\n");
|
||||
buffer.append(" method '" + methodName + "' {\n");
|
||||
for (const auto& [offset, line] : methodMap) {
|
||||
std::string strOffset = offsetsToHex
|
||||
? std::to_string(offset) // hex formatting omitted for brevity
|
||||
: std::to_string(line);
|
||||
buffer.append(" " + strOffset + " " + std::to_string(line + offsetTotal_) + "\n");
|
||||
}
|
||||
buffer.append(" }\n");
|
||||
firstMethod = false;
|
||||
}
|
||||
buffer.append("}\n\n");
|
||||
}
|
||||
|
||||
buffer.append("Lines mapping:\n");
|
||||
for (const auto& [orig, decomp] : linesMapping_) {
|
||||
buffer.append(std::to_string(orig) + " <-> " + std::to_string(decomp + offsetTotal_ + 1) + "\n");
|
||||
}
|
||||
|
||||
if (!unmappedLines_.empty()) {
|
||||
buffer.append("Not mapped:\n");
|
||||
for (int line : unmappedLines_) {
|
||||
if (linesMapping_.find(line) == linesMapping_.end()) {
|
||||
buffer.append(std::to_string(line) + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> BytecodeSourceMapper::getOriginalLinesMapping() const {
|
||||
std::vector<int> res;
|
||||
res.reserve(linesMapping_.size() * 2);
|
||||
for (const auto& [orig, decomp] : linesMapping_) {
|
||||
res.push_back(orig);
|
||||
res.push_back(decomp + offsetTotal_ + 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class TextBuffer;
|
||||
class BytecodeMappingTracer;
|
||||
|
||||
class BytecodeSourceMapper {
|
||||
public:
|
||||
void addMapping(const std::string& className, const std::string& methodName,
|
||||
int bytecodeOffset, int sourceLine);
|
||||
|
||||
void addTracer(const std::string& className, const std::string& methodName,
|
||||
const BytecodeMappingTracer& tracer);
|
||||
|
||||
void dumpMapping(TextBuffer& buffer, bool offsetsToHex) const;
|
||||
void addTotalOffset(int offsetTotal) { offsetTotal_ += offsetTotal; }
|
||||
|
||||
std::vector<int> getOriginalLinesMapping() const;
|
||||
|
||||
private:
|
||||
int offsetTotal_ = 0;
|
||||
// class -> method -> (bytecodeOffset -> sourceLine)
|
||||
std::map<std::string, std::map<std::string, std::map<int,int>>> mapping_;
|
||||
std::unordered_map<int,int> linesMapping_;
|
||||
std::set<int> unmappedLines_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,163 @@
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
project(fernflower CXX)
|
||||
|
||||
add_library(fernflower STATIC
|
||||
# Core infrastructure
|
||||
CancellationManager.cpp
|
||||
Fernflower.cpp
|
||||
BaseDecompiler.cpp
|
||||
IFernflowerLogger.cpp
|
||||
InterpreterUtil.cpp
|
||||
TextBuffer.cpp
|
||||
TextUtil.cpp
|
||||
DecompilerContext.cpp
|
||||
# Type system
|
||||
VarType.cpp
|
||||
FieldDescriptor.cpp
|
||||
MethodDescriptor.cpp
|
||||
GenericType.cpp
|
||||
GenericClassDescriptor.cpp
|
||||
GenericFieldDescriptor.cpp
|
||||
GenericMethodDescriptor.cpp
|
||||
GenericMain.cpp
|
||||
# Constant pool
|
||||
LinkConstant.cpp
|
||||
PrimitiveConstant.cpp
|
||||
PooledConstant.cpp
|
||||
ConstantPool.cpp
|
||||
PoolInterceptor.cpp
|
||||
# Data structures
|
||||
DataInputFullStream.cpp
|
||||
DataPoint.cpp
|
||||
StartEndPair.cpp
|
||||
LimitContainer.cpp
|
||||
SFormsFastMapDirect.cpp
|
||||
# Struct (class file reading)
|
||||
StructMember.cpp
|
||||
StructField.cpp
|
||||
StructMethod.cpp
|
||||
StructClass.cpp
|
||||
StructContext.cpp
|
||||
ContextUnit.cpp
|
||||
LazyLoader.cpp
|
||||
StructGeneralAttribute.cpp
|
||||
StructAnnotationAttribute.cpp
|
||||
StructAnnDefaultAttribute.cpp
|
||||
StructAnnotationParameterAttribute.cpp
|
||||
StructBootstrapMethodsAttribute.cpp
|
||||
StructCodeAttribute.cpp
|
||||
StructConstantValueAttribute.cpp
|
||||
StructEnclosingMethodAttribute.cpp
|
||||
StructExceptionsAttribute.cpp
|
||||
StructGenericSignatureAttribute.cpp
|
||||
StructInnerClassesAttribute.cpp
|
||||
StructLineNumberTableAttribute.cpp
|
||||
StructLocalVariableTableAttribute.cpp
|
||||
StructLocalVariableTypeTableAttribute.cpp
|
||||
StructMethodParametersAttribute.cpp
|
||||
StructModuleAttribute.cpp
|
||||
StructPermittedSubclassesAttribute.cpp
|
||||
StructRecordAttribute.cpp
|
||||
StructRecordComponent.cpp
|
||||
StructTypeAnnotationAttribute.cpp
|
||||
StructTypePathEntry.cpp
|
||||
# Type annotations
|
||||
TargetInfo.cpp
|
||||
TypeAnnotation.cpp
|
||||
TypeAnnotationWriteHelper.cpp
|
||||
# Instruction / CFG
|
||||
Instruction.cpp
|
||||
JumpInstruction.cpp
|
||||
SwitchInstruction.cpp
|
||||
InstructionSequence.cpp
|
||||
SimpleInstructionSequence.cpp
|
||||
FullInstructionSequence.cpp
|
||||
ExceptionHandler.cpp
|
||||
ExceptionRangeCFG.cpp
|
||||
ExceptionTable.cpp
|
||||
BasicBlock.cpp
|
||||
ControlFlowGraph.cpp
|
||||
InstructionImpact.cpp
|
||||
# Statement hierarchy
|
||||
StatEdge.cpp
|
||||
Statement.cpp
|
||||
BasicBlockStatement.cpp
|
||||
SequenceStatement.cpp
|
||||
DummyExitStatement.cpp
|
||||
RootStatement.cpp
|
||||
IfStatement.cpp
|
||||
DoStatement.cpp
|
||||
SwitchStatement.cpp
|
||||
CatchStatement.cpp
|
||||
CatchAllStatement.cpp
|
||||
SynchronizedStatement.cpp
|
||||
# Expression hierarchy
|
||||
Exprent.cpp
|
||||
AnnotationExprent.cpp
|
||||
ArrayExprent.cpp
|
||||
AssignmentExprent.cpp
|
||||
ConstExprent.cpp
|
||||
ExitExprent.cpp
|
||||
FieldExprent.cpp
|
||||
FunctionExprent.cpp
|
||||
IfExprent.cpp
|
||||
InvocationExprent.cpp
|
||||
MonitorExprent.cpp
|
||||
NewExprent.cpp
|
||||
SwitchExprent.cpp
|
||||
VarExprent.cpp
|
||||
# Expression processing
|
||||
ExprProcessor.cpp
|
||||
# Control flow analysis
|
||||
StrongConnectivityHelper.cpp
|
||||
GenericDominatorEngine.cpp
|
||||
DeadCodeHelper.cpp
|
||||
DecHelper.cpp
|
||||
FinallyProcessor.cpp
|
||||
SSAConstructorSparseEx.cpp
|
||||
# Statement helpers
|
||||
SequenceHelper.cpp
|
||||
StatementIterator.cpp
|
||||
SwitchHelper.cpp
|
||||
FlattenStatementsHelper.cpp
|
||||
# Direct graph / SSA
|
||||
DirectNode.cpp
|
||||
DirectGraph.cpp
|
||||
# Variable processing
|
||||
VarProcessor.cpp
|
||||
VarNamesCollector.cpp
|
||||
VarVersionsGraph.cpp
|
||||
# Class processing
|
||||
ClassWrapper.cpp
|
||||
MethodWrapper.cpp
|
||||
ClassesProcessor.cpp
|
||||
ClassWriter.cpp
|
||||
IdentifierConverter.cpp
|
||||
MemberConverterHelper.cpp
|
||||
ImportCollector.cpp
|
||||
# Nested / lambda
|
||||
LambdaProcessor.cpp
|
||||
NestedClassProcessor.cpp
|
||||
NestedMemberAccess.cpp
|
||||
# Classpath
|
||||
ClasspathHelper.cpp
|
||||
ClasspathScanner.cpp
|
||||
# Naming
|
||||
JADNameProvider.cpp
|
||||
# Pattern matching
|
||||
MatchEngine.cpp
|
||||
# Export
|
||||
DotExporter.cpp
|
||||
# Bytecode
|
||||
BytecodeMappingTracer.cpp
|
||||
BytecodeSourceMapper.cpp
|
||||
)
|
||||
|
||||
add_library(torchcs::fernflower ALIAS fernflower)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBZIP REQUIRED libzip)
|
||||
|
||||
target_include_directories(fernflower PUBLIC ../ ${LIBZIP_INCLUDE_DIRS})
|
||||
target_link_libraries(fernflower PUBLIC ${LIBZIP_LIBRARIES})
|
||||
target_compile_options(fernflower PRIVATE ${LIBZIP_CFLAGS_OTHER})
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "CancellationManager.h"
|
||||
#include <chrono>
|
||||
|
||||
namespace ff {
|
||||
|
||||
namespace {
|
||||
long long currentTimeMillis() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
TimeoutCancellationManager::TimeoutCancellationManager(int maxMethodTimeoutSec)
|
||||
: maxMillis_(static_cast<long long>(maxMethodTimeoutSec) * 1000LL)
|
||||
, startMillis_(0) {}
|
||||
|
||||
void TimeoutCancellationManager::checkCanceled() {
|
||||
if (maxMillis_ <= 0 || startMillis_ <= 0) return;
|
||||
if (currentTimeMillis() - startMillis_ > maxMillis_) {
|
||||
throw TimeExceedException();
|
||||
}
|
||||
}
|
||||
|
||||
void TimeoutCancellationManager::startMethod(
|
||||
const std::string& /*className*/, const std::string& /*methodName*/) {
|
||||
startMillis_ = currentTimeMillis();
|
||||
}
|
||||
|
||||
void TimeoutCancellationManager::finishMethod(
|
||||
const std::string& /*className*/, const std::string& /*methodName*/) {
|
||||
startMillis_ = 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<CancellationManager> CancellationManager::getSimpleWithTimeout(int maxMethodTimeoutSec) {
|
||||
return std::make_unique<TimeoutCancellationManager>(maxMethodTimeoutSec);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class CancellationManager {
|
||||
public:
|
||||
virtual ~CancellationManager() = default;
|
||||
|
||||
class CanceledException : public std::runtime_error {
|
||||
public:
|
||||
CanceledException() : std::runtime_error("Decompilation cancelled") {}
|
||||
explicit CanceledException(const std::exception& cause)
|
||||
: std::runtime_error(cause.what()) {}
|
||||
};
|
||||
|
||||
class TimeExceedException : public CanceledException {};
|
||||
|
||||
virtual void checkCanceled() = 0;
|
||||
virtual void startMethod(const std::string& className, const std::string& methodName) = 0;
|
||||
virtual void finishMethod(const std::string& className, const std::string& methodName) = 0;
|
||||
|
||||
static std::unique_ptr<CancellationManager> getSimpleWithTimeout(int maxMethodTimeoutSec);
|
||||
};
|
||||
|
||||
// Concrete implementation — must live outside CancellationManager so it can inherit from it.
|
||||
class TimeoutCancellationManager : public CancellationManager {
|
||||
public:
|
||||
explicit TimeoutCancellationManager(int maxMethodTimeoutSec);
|
||||
|
||||
void checkCanceled() override;
|
||||
void startMethod(const std::string& className, const std::string& methodName) override;
|
||||
void finishMethod(const std::string& className, const std::string& methodName) override;
|
||||
|
||||
private:
|
||||
long long maxMillis_;
|
||||
long long startMillis_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "CatchAllStatement.h"
|
||||
#include "DecHelper.h"
|
||||
#include "StatEdge.h"
|
||||
#include "TextBuffer.h"
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// Local helper: mirrors DecHelper.getUniquePredExceptions
|
||||
static std::unordered_set<Statement*> getUniquePredExceptions(Statement* head) {
|
||||
std::unordered_set<Statement*> setHandlers;
|
||||
for (Statement* s : head->getNeighbours(StatEdge::EdgeType::EXCEPTION, StatEdge::EdgeDirection::FORWARD)) {
|
||||
setHandlers.insert(s);
|
||||
}
|
||||
// Remove handlers that have more than one exception predecessor
|
||||
for (auto it = setHandlers.begin(); it != setHandlers.end(); ) {
|
||||
if ((*it)->getPredecessorEdges(StatEdge::EdgeType::EXCEPTION).size() > 1) {
|
||||
it = setHandlers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return setHandlers;
|
||||
}
|
||||
|
||||
// Local helper: mirrors DecHelper.invalidHeadMerge
|
||||
static Statement* findIfHead(Statement* head) {
|
||||
while (head != nullptr && head->type != Statement::StatementType::IF) {
|
||||
if (head->type != Statement::StatementType::SEQUENCE) {
|
||||
return nullptr;
|
||||
}
|
||||
head = head->getFirst();
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
static bool invalidHeadMerge(Statement* head) {
|
||||
Statement* ifhead = findIfHead(head);
|
||||
if (ifhead == nullptr) return false;
|
||||
return head->getContinueSet().count(ifhead->getFirst()) > 0;
|
||||
}
|
||||
|
||||
CatchAllStatement::CatchAllStatement() : Statement(StatementType::CATCH_ALL) {}
|
||||
|
||||
CatchAllStatement* CatchAllStatement::create(Statement* head, Statement* next, Statement* handler) {
|
||||
auto* stat = new CatchAllStatement();
|
||||
stat->first_ = head;
|
||||
stat->handler_ = handler;
|
||||
stat->stats.addWithKey(head, head->id);
|
||||
if (handler) stat->stats.addWithKey(handler, handler->id);
|
||||
if (next) stat->post_ = next;
|
||||
return stat;
|
||||
}
|
||||
|
||||
Statement* CatchAllStatement::isHead(Statement* head) {
|
||||
if (head->getLastBasicType() != StatementType::GENERAL) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unordered_set<Statement*> setHandlers = getUniquePredExceptions(head);
|
||||
if (setHandlers.size() != 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto& edge : head->getSuccessorEdges(StatEdge::EdgeType::EXCEPTION)) {
|
||||
Statement* exc = edge->getDestination();
|
||||
|
||||
if (edge->getExceptions() == nullptr &&
|
||||
exc->getLastBasicType() == StatementType::GENERAL &&
|
||||
setHandlers.count(exc)) {
|
||||
|
||||
auto lstSuccs = exc->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
if (lstSuccs.empty() || lstSuccs[0]->getType() != StatEdge::EdgeType::REGULAR) {
|
||||
|
||||
if (head->isMonitorEnter() || exc->isMonitorEnter()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (invalidHeadMerge(head)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Statement*> lst = {head, exc};
|
||||
if (DecHelper::checkStatementExceptions(lst)) {
|
||||
// Determine post (regular successor of head)
|
||||
Statement* next = nullptr;
|
||||
auto headSuccs = head->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
if (!headSuccs.empty() && headSuccs[0]->getType() == StatEdge::EdgeType::REGULAR) {
|
||||
next = headSuccs[0]->getDestination();
|
||||
}
|
||||
return CatchAllStatement::create(head, next, exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TextBuffer* CatchAllStatement::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
return new TextBuffer();
|
||||
}
|
||||
|
||||
Statement* CatchAllStatement::getSimpleCopy() {
|
||||
return new CatchAllStatement();
|
||||
}
|
||||
|
||||
void CatchAllStatement::initExprents() {}
|
||||
void CatchAllStatement::replaceExprent(Exprent* oldexpr, Exprent* newexpr) {}
|
||||
|
||||
std::vector<IMatchable*> CatchAllStatement::getSequentialObjects() {
|
||||
return Statement::getSequentialObjects();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Statement.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Exprent;
|
||||
|
||||
class CatchAllStatement : public Statement {
|
||||
public:
|
||||
enum class CatchAllType { FINALLY, MONITOR };
|
||||
|
||||
CatchAllStatement();
|
||||
static CatchAllStatement* create(Statement* head, Statement* next, Statement* handler);
|
||||
static Statement* isHead(Statement* head);
|
||||
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
Statement* getSimpleCopy() override;
|
||||
void initExprents() override;
|
||||
void replaceExprent(Exprent* oldexpr, Exprent* newexpr) override;
|
||||
std::vector<IMatchable*> getSequentialObjects() override;
|
||||
|
||||
Statement* getHandler() const { return handler_; }
|
||||
CatchAllType getCatchType() const { return catchtype_; }
|
||||
void setCatchType(CatchAllType t) { catchtype_ = t; }
|
||||
bool isMonitor() const { return catchtype_ == CatchAllType::MONITOR; }
|
||||
Exprent* getMonitorVar() const { return monitorVar_; }
|
||||
void setMonitorVar(Exprent* e) { monitorVar_ = e; }
|
||||
|
||||
private:
|
||||
Statement* handler_ = nullptr;
|
||||
CatchAllType catchtype_ = CatchAllType::FINALLY;
|
||||
Exprent* monitorVar_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "CatchStatement.h"
|
||||
#include "DecHelper.h"
|
||||
#include "StatEdge.h"
|
||||
#include "TextBuffer.h"
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// Local helper: mirrors DecHelper.getUniquePredExceptions
|
||||
static std::unordered_set<Statement*> getUniquePredExceptionsCatch(Statement* head) {
|
||||
std::unordered_set<Statement*> setHandlers;
|
||||
for (Statement* s : head->getNeighbours(StatEdge::EdgeType::EXCEPTION, StatEdge::EdgeDirection::FORWARD)) {
|
||||
setHandlers.insert(s);
|
||||
}
|
||||
for (auto it = setHandlers.begin(); it != setHandlers.end(); ) {
|
||||
if ((*it)->getPredecessorEdges(StatEdge::EdgeType::EXCEPTION).size() > 1) {
|
||||
it = setHandlers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return setHandlers;
|
||||
}
|
||||
|
||||
// Local helper: mirrors DecHelper.invalidHeadMerge
|
||||
static Statement* findIfHeadCatch(Statement* head) {
|
||||
while (head != nullptr && head->type != Statement::StatementType::IF) {
|
||||
if (head->type != Statement::StatementType::SEQUENCE) {
|
||||
return nullptr;
|
||||
}
|
||||
head = head->getFirst();
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
static bool invalidHeadMergeCatch(Statement* head) {
|
||||
Statement* ifhead = findIfHeadCatch(head);
|
||||
if (ifhead == nullptr) return false;
|
||||
return head->getContinueSet().count(ifhead->getFirst()) > 0;
|
||||
}
|
||||
|
||||
CatchStatement::CatchStatement() : Statement(StatementType::TRY_CATCH) {}
|
||||
|
||||
CatchStatement* CatchStatement::create(Statement* head, Statement* next,
|
||||
const std::vector<Statement*>& handlers) {
|
||||
auto* stat = new CatchStatement();
|
||||
stat->first_ = head;
|
||||
stat->stats.addWithKey(head, head->id);
|
||||
if (next) stat->post_ = next;
|
||||
for (Statement* h : handlers) {
|
||||
stat->stats.addWithKey(h, h->id);
|
||||
}
|
||||
return stat;
|
||||
}
|
||||
|
||||
Statement* CatchStatement::isHead(Statement* head) {
|
||||
if (head->getLastBasicType() != StatementType::GENERAL) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unordered_set<Statement*> setHandlers = getUniquePredExceptionsCatch(head);
|
||||
if (setHandlers.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int hnextcount = 0;
|
||||
Statement* next = nullptr;
|
||||
|
||||
auto lstHeadSuccs = head->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
if (!lstHeadSuccs.empty() && lstHeadSuccs[0]->getType() == StatEdge::EdgeType::REGULAR) {
|
||||
next = lstHeadSuccs[0]->getDestination();
|
||||
hnextcount = 2;
|
||||
}
|
||||
|
||||
for (auto& edge : head->getSuccessorEdges(StatEdge::EdgeType::EXCEPTION)) {
|
||||
Statement* stat = edge->getDestination();
|
||||
bool handlerok = true;
|
||||
|
||||
if (edge->getExceptions() != nullptr && setHandlers.count(stat)) {
|
||||
if (stat->getLastBasicType() != StatementType::GENERAL) {
|
||||
handlerok = false;
|
||||
} else {
|
||||
auto lstStatSuccs = stat->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
if (!lstStatSuccs.empty() && lstStatSuccs[0]->getType() == StatEdge::EdgeType::REGULAR) {
|
||||
Statement* statn = lstStatSuccs[0]->getDestination();
|
||||
if (next == nullptr) {
|
||||
next = statn;
|
||||
} else if (next != statn) {
|
||||
handlerok = false;
|
||||
}
|
||||
if (handlerok) {
|
||||
hnextcount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handlerok = false;
|
||||
}
|
||||
|
||||
if (!handlerok) {
|
||||
setHandlers.erase(stat);
|
||||
}
|
||||
}
|
||||
|
||||
if (hnextcount != 1 && !setHandlers.empty()) {
|
||||
std::vector<Statement*> lst;
|
||||
lst.push_back(head);
|
||||
for (Statement* h : setHandlers) {
|
||||
lst.push_back(h);
|
||||
}
|
||||
|
||||
for (Statement* st : lst) {
|
||||
if (st->isMonitorEnter()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidHeadMergeCatch(head)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (DecHelper::checkStatementExceptions(lst)) {
|
||||
std::vector<Statement*> handlers(setHandlers.begin(), setHandlers.end());
|
||||
return CatchStatement::create(head, next, handlers);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TextBuffer* CatchStatement::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
return new TextBuffer();
|
||||
}
|
||||
|
||||
Statement* CatchStatement::getSimpleCopy() {
|
||||
return new CatchStatement();
|
||||
}
|
||||
|
||||
void CatchStatement::initExprents() {}
|
||||
void CatchStatement::replaceExprent(Exprent* oldexpr, Exprent* newexpr) {}
|
||||
|
||||
std::vector<IMatchable*> CatchStatement::getSequentialObjects() {
|
||||
return Statement::getSequentialObjects();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Statement.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Exprent;
|
||||
|
||||
class CatchStatement : public Statement {
|
||||
public:
|
||||
enum class CatchStatementType { NORMAL, RESOURCES };
|
||||
|
||||
CatchStatement();
|
||||
static CatchStatement* create(Statement* head, Statement* next,
|
||||
const std::vector<Statement*>& handlers);
|
||||
static Statement* isHead(Statement* head);
|
||||
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
Statement* getSimpleCopy() override;
|
||||
void initExprents() override;
|
||||
void replaceExprent(Exprent* oldexpr, Exprent* newexpr) override;
|
||||
std::vector<IMatchable*> getSequentialObjects() override;
|
||||
|
||||
std::vector<std::vector<std::string>>& getExctstrings() { return exctstrings_; }
|
||||
std::vector<Exprent*>& getVars() { return vars_; }
|
||||
std::vector<Exprent*>& getResources() { return resources_; }
|
||||
CatchStatementType getTryType() const { return tryType_; }
|
||||
void setTryType(CatchStatementType t) { tryType_ = t; }
|
||||
|
||||
private:
|
||||
std::vector<std::vector<std::string>> exctstrings_;
|
||||
std::vector<Exprent*> vars_;
|
||||
std::vector<Exprent*> resources_;
|
||||
CatchStatementType tryType_ = CatchStatementType::NORMAL;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "VarType.h"
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Exprent;
|
||||
|
||||
class CheckTypesResult {
|
||||
public:
|
||||
struct ExprentTypePair {
|
||||
Exprent* exprent;
|
||||
VarType type;
|
||||
ExprentTypePair(Exprent* e, VarType t) : exprent(e), type(std::move(t)) {}
|
||||
};
|
||||
|
||||
std::vector<ExprentTypePair> lstMinTypeExprents;
|
||||
std::vector<ExprentTypePair> lstMaxTypeExprents;
|
||||
|
||||
bool isEmpty() const { return lstMinTypeExprents.empty() && lstMaxTypeExprents.empty(); }
|
||||
|
||||
void addMinTypeExprent(Exprent* exprent, const VarType& type) {
|
||||
lstMinTypeExprents.emplace_back(exprent, type);
|
||||
}
|
||||
void addMaxTypeExprent(Exprent* exprent, const VarType& type) {
|
||||
lstMaxTypeExprents.emplace_back(exprent, type);
|
||||
}
|
||||
|
||||
const std::vector<ExprentTypePair>& getMinTypeExprents() const { return lstMinTypeExprents; }
|
||||
const std::vector<ExprentTypePair>& getMaxTypeExprents() const { return lstMaxTypeExprents; }
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class ClassFormatException : public std::runtime_error {
|
||||
public:
|
||||
explicit ClassFormatException(const std::string& message)
|
||||
: std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
struct ClassNameConstants {
|
||||
static inline const std::string JAVA_LANG_STRING = "java/lang/String";
|
||||
static inline const std::string JAVA_LANG_CHARACTER = "java/lang/Character";
|
||||
static inline const std::string JAVA_UTIL_OBJECTS = "java/util/Objects";
|
||||
static inline const std::string JAVA_LANG_OBJECT = "java/lang/Object";
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ClassWrapper.h"
|
||||
#include "BasicBlock.h"
|
||||
#include "BasicBlockStatement.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "ControlFlowGraph.h"
|
||||
#include "CounterContainer.h"
|
||||
#include "DeadCodeHelper.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "DummyExitStatement.h"
|
||||
#include "FinallyProcessor.h"
|
||||
#include "IFernflowerLogger.h"
|
||||
#include "InterpreterUtil.h"
|
||||
#include "MethodDescriptor.h"
|
||||
#include "MethodWrapper.h"
|
||||
#include "RootStatement.h"
|
||||
#include "SequenceHelper.h"
|
||||
#include "SequenceStatement.h"
|
||||
#include "SSAConstructorSparseEx.h"
|
||||
#include "StatEdge.h"
|
||||
#include "StructClass.h"
|
||||
#include "StructMethod.h"
|
||||
#include "VarProcessor.h"
|
||||
#include "VarVersion.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
ClassWrapper::ClassWrapper(StructClass* classStruct)
|
||||
: classStruct_(classStruct) {}
|
||||
|
||||
// Build a flat linear RootStatement from CFG basic blocks.
|
||||
// This is a functional fallback for DomHelper::parseGraph which is not yet ported.
|
||||
// TODO: full implementation via DomHelper dominator-tree based graph parsing
|
||||
static std::unique_ptr<RootStatement> buildLinearRoot(ControlFlowGraph& graph) {
|
||||
auto rpo = graph.getReversePostOrder();
|
||||
|
||||
auto* dummyExit = new DummyExitStatement();
|
||||
|
||||
if (rpo.empty()) {
|
||||
auto* seqSt = new SequenceStatement();
|
||||
return std::make_unique<RootStatement>(seqSt, dummyExit);
|
||||
}
|
||||
|
||||
std::vector<Statement*> stmts;
|
||||
stmts.reserve(rpo.size());
|
||||
for (BasicBlock* bb : rpo) {
|
||||
if (bb == graph.getLast()) continue;
|
||||
stmts.push_back(new BasicBlockStatement(bb));
|
||||
}
|
||||
|
||||
if (stmts.empty()) {
|
||||
auto* seqSt = new SequenceStatement();
|
||||
return std::make_unique<RootStatement>(seqSt, dummyExit);
|
||||
}
|
||||
|
||||
// Wire linear REGULAR edges between consecutive statements so that
|
||||
// SequenceHelper and toJava traversals work correctly.
|
||||
for (size_t i = 0; i + 1 < stmts.size(); i++) {
|
||||
auto edge = std::make_shared<StatEdge>(
|
||||
StatEdge::EdgeType::REGULAR, stmts[i], stmts[i + 1]);
|
||||
stmts[i]->addSuccessor(edge);
|
||||
stmts[i + 1]->addPredecessor(edge);
|
||||
}
|
||||
|
||||
Statement* body;
|
||||
if (stmts.size() == 1) {
|
||||
body = stmts[0];
|
||||
} else {
|
||||
body = new SequenceStatement(stmts);
|
||||
body->setAllParent();
|
||||
}
|
||||
|
||||
return std::make_unique<RootStatement>(body, dummyExit);
|
||||
}
|
||||
|
||||
void ClassWrapper::init() {
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
"Processing class: " + classStruct_->qualifiedName,
|
||||
IFernflowerLogger::Severity::TRACE);
|
||||
|
||||
for (auto& mtPtr : classStruct_->getMethods()) {
|
||||
StructMethod* mt = mtPtr.get();
|
||||
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
" method: " + mt->getName() + " " + mt->getDescriptor(),
|
||||
IFernflowerLogger::Severity::TRACE);
|
||||
|
||||
MethodDescriptor md = MethodDescriptor::parseDescriptor(mt->getDescriptor());
|
||||
auto varProc = std::make_unique<VarProcessor>(classStruct_, mt, &md);
|
||||
|
||||
DecompilerContext::startMethod(varProc.get());
|
||||
|
||||
auto wrapper = std::make_unique<MethodWrapper>(mt);
|
||||
wrapper->varProc = std::move(varProc);
|
||||
|
||||
bool isError = false;
|
||||
|
||||
if (mt->containsCode()) {
|
||||
try {
|
||||
mt->expandData(*classStruct_);
|
||||
InstructionSequence* seq = mt->getInstructionSequence();
|
||||
if (!seq) {
|
||||
isError = true;
|
||||
} else {
|
||||
ControlFlowGraph graph(seq);
|
||||
|
||||
DeadCodeHelper::removeDeadBlocks(graph);
|
||||
|
||||
if (!classStruct_->isVersion7()) {
|
||||
graph.inlineJsr(classStruct_, mt);
|
||||
}
|
||||
|
||||
DeadCodeHelper::removeEmptyBlocks(graph);
|
||||
|
||||
DecompilerContext::getCounterContainer()->setCounter(
|
||||
CounterContainer::VAR_COUNTER, mt->getLocalVariables());
|
||||
|
||||
// TODO: full implementation via DomHelper::parseGraph
|
||||
std::unique_ptr<RootStatement> root = buildLinearRoot(graph);
|
||||
|
||||
FinallyProcessor fProc(classStruct_, mt);
|
||||
// iterateGraph is a stub; loop body never repeats
|
||||
while (fProc.iterateGraph(root.get())) {
|
||||
root = buildLinearRoot(graph);
|
||||
}
|
||||
|
||||
SequenceHelper::condenseSequences(root.get());
|
||||
|
||||
SSAConstructorSparseEx ssaConstructor;
|
||||
ssaConstructor.splitVariables(root.get(), mt);
|
||||
|
||||
wrapper->varProc->setVarVersions(root.get());
|
||||
|
||||
wrapper->root = std::move(root);
|
||||
|
||||
mt->releaseResources();
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
"Method " + mt->getName() + " " + mt->getDescriptor() +
|
||||
" in class " + classStruct_->qualifiedName +
|
||||
" couldn't be decompiled: " + e.what(),
|
||||
IFernflowerLogger::Severity::WARN);
|
||||
isError = true;
|
||||
} catch (...) {
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
"Method " + mt->getName() + " " + mt->getDescriptor() +
|
||||
" in class " + classStruct_->qualifiedName +
|
||||
" couldn't be decompiled.",
|
||||
IFernflowerLogger::Severity::WARN);
|
||||
isError = true;
|
||||
}
|
||||
} else {
|
||||
// No code: assign parameter names
|
||||
VarNamesCollector& vc = wrapper->varProc->getVarNamesCollector();
|
||||
int varIndex = 0;
|
||||
if (!mt->hasModifier(CodeConstants::ACC_STATIC)) {
|
||||
wrapper->varProc->setVarName(VarVersion(0, 0), vc.getFreeName("this"));
|
||||
varIndex = 1;
|
||||
}
|
||||
for (size_t i = 0; i < md.params.size(); i++) {
|
||||
wrapper->varProc->setVarName(
|
||||
VarVersion(varIndex, 0),
|
||||
vc.getFreeName("param" + std::to_string(varIndex)));
|
||||
varIndex += md.params[i].getStackSize();
|
||||
}
|
||||
}
|
||||
|
||||
wrapper->decompiledWithErrors = isError;
|
||||
|
||||
std::string key = InterpreterUtil::makeUniqueKey(mt->getName(), mt->getDescriptor());
|
||||
methods_.addWithKey(std::move(wrapper), key);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "MethodWrapper.h"
|
||||
#include "VBStyleCollection.h"
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Exprent;
|
||||
class StructClass;
|
||||
|
||||
class ClassWrapper {
|
||||
public:
|
||||
explicit ClassWrapper(StructClass* classStruct);
|
||||
|
||||
void init();
|
||||
|
||||
StructClass* getClassStruct() const { return classStruct_; }
|
||||
const std::unordered_set<std::string>& getHiddenMembers() const { return hiddenMembers_; }
|
||||
std::unordered_set<std::string>& getHiddenMembers() { return hiddenMembers_; }
|
||||
|
||||
VBStyleCollection<std::shared_ptr<Exprent>, std::string>& getStaticFieldInitializers() { return staticFieldInitializers_; }
|
||||
VBStyleCollection<std::shared_ptr<Exprent>, std::string>& getDynamicFieldInitializers() { return dynamicFieldInitializers_; }
|
||||
VBStyleCollection<std::unique_ptr<MethodWrapper>, std::string>& getMethods() { return methods_; }
|
||||
|
||||
private:
|
||||
StructClass* classStruct_;
|
||||
std::unordered_set<std::string> hiddenMembers_;
|
||||
VBStyleCollection<std::shared_ptr<Exprent>, std::string> staticFieldInitializers_;
|
||||
VBStyleCollection<std::shared_ptr<Exprent>, std::string> dynamicFieldInitializers_;
|
||||
VBStyleCollection<std::unique_ptr<MethodWrapper>, std::string> methods_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class StructClass;
|
||||
|
||||
class ClassWrapperNode {
|
||||
public:
|
||||
explicit ClassWrapperNode(StructClass* cl) : classStruct_(cl) {}
|
||||
|
||||
void addSubclass(ClassWrapperNode* sub) { subclasses_.push_back(sub); }
|
||||
|
||||
StructClass* getClassStruct() const { return classStruct_; }
|
||||
const std::vector<ClassWrapperNode*>& getSubclasses() const { return subclasses_; }
|
||||
|
||||
private:
|
||||
StructClass* classStruct_;
|
||||
std::vector<ClassWrapperNode*> subclasses_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,718 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ClassWriter.h"
|
||||
|
||||
#include "AnnotationExprent.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "ClassesProcessor.h"
|
||||
#include "ClassWrapper.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "ExprProcessor.h"
|
||||
#include "GenericClassDescriptor.h"
|
||||
#include "GenericFieldDescriptor.h"
|
||||
#include "GenericMethodDescriptor.h"
|
||||
#include "IFernflowerLogger.h"
|
||||
#include "IFernflowerPreferences.h"
|
||||
#include "ImportCollector.h"
|
||||
#include "InterpreterUtil.h"
|
||||
#include "MethodDescriptor.h"
|
||||
#include "MethodWrapper.h"
|
||||
#include "PoolInterceptor.h"
|
||||
#include "RootStatement.h"
|
||||
#include "StructAnnotationAttribute.h"
|
||||
#include "VarProcessor.h"
|
||||
#include "StructClass.h"
|
||||
#include "StructExceptionsAttribute.h"
|
||||
#include "StructField.h"
|
||||
#include "StructGeneralAttribute.h"
|
||||
#include "StructInnerClassesAttribute.h"
|
||||
#include "StructMethod.h"
|
||||
#include "StructRecordComponent.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "VarType.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Modifier tables (matching Java ClassWriter order)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const std::map<int, std::string> MODIFIERS = {
|
||||
{CodeConstants::ACC_PUBLIC, "public"},
|
||||
{CodeConstants::ACC_PROTECTED, "protected"},
|
||||
{CodeConstants::ACC_PRIVATE, "private"},
|
||||
{CodeConstants::ACC_ABSTRACT, "abstract"},
|
||||
{CodeConstants::ACC_STATIC, "static"},
|
||||
{CodeConstants::ACC_FINAL, "final"},
|
||||
{CodeConstants::ACC_STRICT, "strictfp"},
|
||||
{CodeConstants::ACC_TRANSIENT, "transient"},
|
||||
{CodeConstants::ACC_VOLATILE, "volatile"},
|
||||
{CodeConstants::ACC_SYNCHRONIZED, "synchronized"},
|
||||
{CodeConstants::ACC_NATIVE, "native"},
|
||||
};
|
||||
|
||||
static const int CLASS_ALLOWED =
|
||||
CodeConstants::ACC_PUBLIC | CodeConstants::ACC_PROTECTED | CodeConstants::ACC_PRIVATE |
|
||||
CodeConstants::ACC_ABSTRACT | CodeConstants::ACC_STATIC | CodeConstants::ACC_FINAL |
|
||||
CodeConstants::ACC_STRICT;
|
||||
|
||||
static const int FIELD_ALLOWED =
|
||||
CodeConstants::ACC_PUBLIC | CodeConstants::ACC_PROTECTED | CodeConstants::ACC_PRIVATE |
|
||||
CodeConstants::ACC_STATIC | CodeConstants::ACC_FINAL | CodeConstants::ACC_TRANSIENT |
|
||||
CodeConstants::ACC_VOLATILE;
|
||||
|
||||
static const int METHOD_ALLOWED =
|
||||
CodeConstants::ACC_PUBLIC | CodeConstants::ACC_PROTECTED | CodeConstants::ACC_PRIVATE |
|
||||
CodeConstants::ACC_ABSTRACT | CodeConstants::ACC_STATIC | CodeConstants::ACC_FINAL |
|
||||
CodeConstants::ACC_SYNCHRONIZED | CodeConstants::ACC_NATIVE | CodeConstants::ACC_STRICT;
|
||||
|
||||
static const int CLASS_EXCLUDED = CodeConstants::ACC_ABSTRACT | CodeConstants::ACC_STATIC;
|
||||
static const int FIELD_EXCLUDED = CodeConstants::ACC_PUBLIC | CodeConstants::ACC_STATIC | CodeConstants::ACC_FINAL;
|
||||
static const int METHOD_EXCLUDED = CodeConstants::ACC_PUBLIC | CodeConstants::ACC_ABSTRACT;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Static helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void appendModifiers(TextBuffer& buffer, int flags, int allowed, bool isInterface, int excluded) {
|
||||
flags &= allowed;
|
||||
if (!isInterface) excluded = 0;
|
||||
for (const auto& [modifier, name] : MODIFIERS) {
|
||||
if ((flags & modifier) == modifier && (modifier & excluded) == 0) {
|
||||
buffer.append(name).append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void appendDeprecation(TextBuffer& buffer, int indent) {
|
||||
buffer.appendIndent(indent).append("/** @deprecated */").appendLineSeparator();
|
||||
}
|
||||
|
||||
static void appendComment(TextBuffer& buffer, const std::string& comment, int indent) {
|
||||
buffer.appendIndent(indent).append("// $FF: ").append(comment).appendLineSeparator();
|
||||
}
|
||||
|
||||
static void appendAnnotations(TextBuffer& buffer, int indent, StructMember* mb) {
|
||||
for (const auto& attrName : StructGeneralAttribute::ANNOTATION_ATTRIBUTE_NAMES) {
|
||||
auto* attr = static_cast<StructAnnotationAttribute*>(mb->getAttribute(attrName));
|
||||
if (!attr) continue;
|
||||
for (const auto& annotation : attr->getAnnotations()) {
|
||||
TextBuffer* annBuf = annotation->toJava(indent, &BytecodeMappingTracer::DUMMY);
|
||||
if (annBuf) {
|
||||
buffer.append(*annBuf);
|
||||
delete annBuf;
|
||||
}
|
||||
if (indent < 0) {
|
||||
buffer.append(' ');
|
||||
} else {
|
||||
buffer.appendLineSeparator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string toValidJavaIdentifier(const std::string& name) {
|
||||
if (name.empty()) return name;
|
||||
|
||||
bool changed = false;
|
||||
std::string res;
|
||||
res.reserve(name.size());
|
||||
for (size_t i = 0; i < name.size(); ++i) {
|
||||
char c = name[i];
|
||||
bool valid = (i == 0) ? (std::isalpha(static_cast<unsigned char>(c)) || c == '_' || c == '$')
|
||||
: (std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '$');
|
||||
if (!valid) {
|
||||
changed = true;
|
||||
res += '_';
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
if (!changed) return name;
|
||||
return res + "/* $FF was: " + name + "*/";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ClassWriter constructor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
ClassWriter::ClassWriter(ClassesProcessor* classProcessor)
|
||||
: classProcessor_(classProcessor) {}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// getModifiers (static)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::string ClassWriter::getModifiers(int flags) {
|
||||
std::string result;
|
||||
for (const auto& [modifier, name] : MODIFIERS) {
|
||||
if (flags & modifier) {
|
||||
if (!result.empty()) result += ' ';
|
||||
result += name;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// appendTypeParameters
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void appendTypeParameters(TextBuffer& buffer,
|
||||
const std::vector<std::string>& parameters,
|
||||
const std::vector<std::vector<std::shared_ptr<VarType>>>& bounds) {
|
||||
buffer.append('<');
|
||||
for (size_t i = 0; i < parameters.size(); ++i) {
|
||||
if (i > 0) buffer.append(", ");
|
||||
buffer.append(parameters[i]);
|
||||
if (!bounds.empty() && i < bounds.size()) {
|
||||
const auto& paramBounds = bounds[i];
|
||||
bool hasNonObjectBound = false;
|
||||
for (const auto& b : paramBounds) {
|
||||
if (b && *b != VarType::VARTYPE_OBJECT) {
|
||||
hasNonObjectBound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasNonObjectBound || paramBounds.size() > 1) {
|
||||
buffer.append(" extends ");
|
||||
bool firstBound = true;
|
||||
for (const auto& b : paramBounds) {
|
||||
if (!b) continue;
|
||||
if (!firstBound) buffer.append(" & ");
|
||||
firstBound = false;
|
||||
buffer.append(ExprProcessor::getCastTypeName(*b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer.append('>');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// writeClassDefinition
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void writeClassDefinition(ClassesProcessor::ClassNode* node, TextBuffer& buffer, int indent) {
|
||||
using ClassNode = ClassesProcessor::ClassNode;
|
||||
|
||||
if (node->type == ClassNode::CLASS_ANONYMOUS) {
|
||||
buffer.append(" {").appendLineSeparator();
|
||||
return;
|
||||
}
|
||||
|
||||
ClassWrapper* wrapper = node->wrapper.get();
|
||||
if (!wrapper) {
|
||||
buffer.append(" {").appendLineSeparator();
|
||||
return;
|
||||
}
|
||||
StructClass* cl = wrapper->getClassStruct();
|
||||
|
||||
int flags = (node->type == ClassNode::CLASS_ROOT) ? cl->getAccessFlags() : node->accessFlags;
|
||||
|
||||
bool isDeprecated = cl->hasAttribute(StructGeneralAttribute::ATTRIBUTE_DEPRECATED_NAME);
|
||||
bool isSynthetic = (flags & CodeConstants::ACC_SYNTHETIC) != 0 ||
|
||||
cl->hasAttribute(StructGeneralAttribute::ATTRIBUTE_SYNTHETIC_NAME);
|
||||
bool isEnum = DecompilerContext::getOption(IFernflowerPreferences::DECOMPILE_ENUM) &&
|
||||
(flags & CodeConstants::ACC_ENUM) != 0;
|
||||
bool isInterface = (flags & CodeConstants::ACC_INTERFACE) != 0;
|
||||
bool isAnnotation = (flags & CodeConstants::ACC_ANNOTATION) != 0;
|
||||
|
||||
const auto* components = cl->getRecordComponents();
|
||||
|
||||
if (isDeprecated) {
|
||||
appendDeprecation(buffer, indent);
|
||||
}
|
||||
|
||||
PoolInterceptor* interceptor = DecompilerContext::getPoolInterceptor();
|
||||
if (interceptor) {
|
||||
std::string oldName = interceptor->getOldName(cl->qualifiedName);
|
||||
if (!oldName.empty()) {
|
||||
buffer.appendIndent(indent).append("// $FF: renamed from: ")
|
||||
.append(ExprProcessor::buildJavaClassName(oldName)).appendLineSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
if (isSynthetic) {
|
||||
appendComment(buffer, "synthetic class", indent);
|
||||
}
|
||||
|
||||
appendAnnotations(buffer, indent, cl);
|
||||
|
||||
buffer.appendIndent(indent);
|
||||
|
||||
int printFlags = flags;
|
||||
if (isEnum) {
|
||||
printFlags &= ~CodeConstants::ACC_ABSTRACT;
|
||||
printFlags &= ~CodeConstants::ACC_FINAL;
|
||||
}
|
||||
if (components != nullptr) {
|
||||
printFlags &= ~CodeConstants::ACC_FINAL;
|
||||
}
|
||||
|
||||
appendModifiers(buffer, printFlags, CLASS_ALLOWED, isInterface, CLASS_EXCLUDED);
|
||||
|
||||
const auto* permitted = cl->getPermittedSubclasses();
|
||||
if (permitted != nullptr && !isEnum) {
|
||||
buffer.append("sealed ");
|
||||
} else {
|
||||
// non-sealed: not tracked in C++ ClassNode
|
||||
}
|
||||
|
||||
if (isEnum) {
|
||||
buffer.append("enum ");
|
||||
} else if (isInterface) {
|
||||
if (isAnnotation) buffer.append('@');
|
||||
buffer.append("interface ");
|
||||
} else if (components != nullptr) {
|
||||
buffer.append("record ");
|
||||
} else {
|
||||
buffer.append("class ");
|
||||
}
|
||||
buffer.append(node->simpleName);
|
||||
|
||||
const GenericClassDescriptor* descriptor = cl->getSignature();
|
||||
if (descriptor && !descriptor->fparameters.empty()) {
|
||||
appendTypeParameters(buffer, descriptor->fparameters, descriptor->fbounds);
|
||||
}
|
||||
|
||||
if (components != nullptr) {
|
||||
buffer.append('(');
|
||||
for (size_t i = 0; i < components->size(); ++i) {
|
||||
if (i > 0) buffer.append(", ");
|
||||
const auto* cd = (*components)[i].get();
|
||||
VarType fieldType(cd->getDescriptor(), false);
|
||||
GenericFieldDescriptor* sig = cd->getSignature();
|
||||
if (sig && sig->type) {
|
||||
buffer.append(ExprProcessor::getCastTypeName(*sig->type));
|
||||
} else {
|
||||
buffer.append(ExprProcessor::getCastTypeName(fieldType));
|
||||
}
|
||||
buffer.append(' ').append(cd->getName());
|
||||
}
|
||||
buffer.append(')');
|
||||
}
|
||||
|
||||
buffer.append(' ');
|
||||
|
||||
if (!isEnum && !isInterface && components == nullptr && cl->superClass.has_value()) {
|
||||
VarType supertype(cl->superClass->getString(), true);
|
||||
if (supertype != VarType::VARTYPE_OBJECT) {
|
||||
buffer.append("extends ");
|
||||
if (descriptor && descriptor->superclass) {
|
||||
buffer.append(ExprProcessor::getCastTypeName(*descriptor->superclass));
|
||||
} else {
|
||||
buffer.append(ExprProcessor::getCastTypeName(supertype));
|
||||
}
|
||||
buffer.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAnnotation) {
|
||||
const auto& interfaces = cl->getInterfaces();
|
||||
if (!interfaces.empty()) {
|
||||
buffer.append(isInterface ? "extends " : "implements ");
|
||||
for (size_t i = 0; i < interfaces.size(); ++i) {
|
||||
if (i > 0) buffer.append(", ");
|
||||
if (descriptor && i < descriptor->superinterfaces.size() &&
|
||||
descriptor->superinterfaces[i]) {
|
||||
buffer.append(ExprProcessor::getCastTypeName(*descriptor->superinterfaces[i]));
|
||||
} else {
|
||||
buffer.append(ExprProcessor::getCastTypeName(VarType(cl->getInterface(static_cast<int>(i)), true)));
|
||||
}
|
||||
}
|
||||
buffer.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
buffer.append('{').appendLineSeparator();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fieldToJava
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void fieldToJava(ClassWrapper* wrapper, StructClass* cl, StructField* fd,
|
||||
TextBuffer& buffer, int indent) {
|
||||
bool isInterface = cl->hasModifier(CodeConstants::ACC_INTERFACE);
|
||||
bool isDeprecated = fd->hasAttribute(StructGeneralAttribute::ATTRIBUTE_DEPRECATED_NAME);
|
||||
bool isEnum = fd->hasModifier(CodeConstants::ACC_ENUM) &&
|
||||
DecompilerContext::getOption(IFernflowerPreferences::DECOMPILE_ENUM);
|
||||
|
||||
if (isDeprecated) {
|
||||
appendDeprecation(buffer, indent);
|
||||
}
|
||||
|
||||
PoolInterceptor* interceptor = DecompilerContext::getPoolInterceptor();
|
||||
if (interceptor) {
|
||||
std::string oldName = interceptor->getOldName(
|
||||
cl->qualifiedName + " " + fd->getName() + " " + fd->getDescriptor());
|
||||
if (!oldName.empty()) {
|
||||
buffer.appendIndent(indent).append("// $FF: renamed from: ")
|
||||
.append(fd->getName()).append(' ')
|
||||
.appendLineSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
if (fd->isSynthetic()) {
|
||||
appendComment(buffer, "synthetic field", indent);
|
||||
}
|
||||
|
||||
appendAnnotations(buffer, indent, fd);
|
||||
|
||||
buffer.appendIndent(indent);
|
||||
|
||||
VarType fieldType(fd->getDescriptor(), false);
|
||||
GenericFieldDescriptor* gfd = fd->getSignature();
|
||||
|
||||
if (!isEnum) {
|
||||
appendModifiers(buffer, fd->getAccessFlags(), FIELD_ALLOWED, isInterface, FIELD_EXCLUDED);
|
||||
if (gfd && gfd->type) {
|
||||
buffer.append(ExprProcessor::getCastTypeName(*gfd->type));
|
||||
} else {
|
||||
buffer.append(ExprProcessor::getCastTypeName(fieldType));
|
||||
}
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(fd->getName());
|
||||
|
||||
std::string key = InterpreterUtil::makeUniqueKey(fd->getName(), fd->getDescriptor());
|
||||
std::shared_ptr<Exprent>* initPtr = nullptr;
|
||||
if (fd->hasModifier(CodeConstants::ACC_STATIC)) {
|
||||
initPtr = wrapper->getStaticFieldInitializers().getWithKey(key);
|
||||
} else {
|
||||
initPtr = wrapper->getDynamicFieldInitializers().getWithKey(key);
|
||||
}
|
||||
|
||||
if (initPtr && *initPtr) {
|
||||
Exprent* initializer = initPtr->get();
|
||||
buffer.append(" = ");
|
||||
TextBuffer* initBuf = initializer->toJava(indent, &BytecodeMappingTracer::DUMMY);
|
||||
if (initBuf) {
|
||||
buffer.append(*initBuf);
|
||||
delete initBuf;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEnum) {
|
||||
buffer.append(';').appendLineSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// methodToJava
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static bool methodToJava(ClassesProcessor::ClassNode* node, StructMethod* mt,
|
||||
TextBuffer& buffer, int indent) {
|
||||
using ClassNode = ClassesProcessor::ClassNode;
|
||||
ClassWrapper* wrapper = node->wrapper.get();
|
||||
if (!wrapper) return false;
|
||||
StructClass* cl = wrapper->getClassStruct();
|
||||
|
||||
std::string key = InterpreterUtil::makeUniqueKey(mt->getName(), mt->getDescriptor());
|
||||
auto* mwPtr = wrapper->getMethods().getWithKey(key);
|
||||
if (!mwPtr) return false;
|
||||
MethodWrapper* methodWrapper = mwPtr->get();
|
||||
if (!methodWrapper) return false;
|
||||
|
||||
bool isInterface = cl->hasModifier(CodeConstants::ACC_INTERFACE);
|
||||
bool isAnnotation = cl->hasModifier(CodeConstants::ACC_ANNOTATION);
|
||||
bool isDeprecated = mt->hasAttribute(StructGeneralAttribute::ATTRIBUTE_DEPRECATED_NAME);
|
||||
|
||||
MethodDescriptor md = MethodDescriptor::parseDescriptor(mt->getDescriptor());
|
||||
|
||||
int flags = mt->getAccessFlags();
|
||||
if ((flags & CodeConstants::ACC_NATIVE) != 0) {
|
||||
flags &= ~CodeConstants::ACC_STRICT;
|
||||
}
|
||||
bool isClinit = (mt->getName() == CodeConstants::CLINIT_NAME);
|
||||
if (isClinit) {
|
||||
flags &= CodeConstants::ACC_STATIC;
|
||||
}
|
||||
|
||||
if (isDeprecated) {
|
||||
appendDeprecation(buffer, indent);
|
||||
}
|
||||
|
||||
PoolInterceptor* interceptor = DecompilerContext::getPoolInterceptor();
|
||||
if (interceptor) {
|
||||
std::string oldName = interceptor->getOldName(
|
||||
cl->qualifiedName + " " + mt->getName() + " " + mt->getDescriptor());
|
||||
if (!oldName.empty()) {
|
||||
buffer.appendIndent(indent).append("// $FF: renamed from: ")
|
||||
.append(mt->getName()).appendLineSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
bool isSynthetic = (flags & CodeConstants::ACC_SYNTHETIC) != 0 ||
|
||||
mt->hasAttribute(StructGeneralAttribute::ATTRIBUTE_SYNTHETIC_NAME);
|
||||
bool isBridge = (flags & CodeConstants::ACC_BRIDGE) != 0;
|
||||
if (isSynthetic) appendComment(buffer, "synthetic method", indent);
|
||||
if (isBridge) appendComment(buffer, "bridge method", indent);
|
||||
|
||||
const GenericMethodDescriptor* descriptor = mt->getSignature();
|
||||
|
||||
appendAnnotations(buffer, indent, mt);
|
||||
buffer.appendIndent(indent);
|
||||
appendModifiers(buffer, flags, METHOD_ALLOWED, isInterface, METHOD_EXCLUDED);
|
||||
|
||||
if (isInterface && !mt->hasModifier(CodeConstants::ACC_STATIC) &&
|
||||
!mt->hasModifier(CodeConstants::ACC_PRIVATE) && mt->containsCode()) {
|
||||
buffer.append("default ");
|
||||
}
|
||||
|
||||
std::string name = mt->getName();
|
||||
bool isInit = false;
|
||||
bool isDInit = false;
|
||||
|
||||
if (name == CodeConstants::INIT_NAME) {
|
||||
if (node->type == ClassNode::CLASS_ANONYMOUS) {
|
||||
name = "";
|
||||
isDInit = true;
|
||||
} else {
|
||||
name = node->simpleName;
|
||||
isInit = true;
|
||||
}
|
||||
} else if (isClinit) {
|
||||
name = "";
|
||||
}
|
||||
|
||||
bool throwsExceptions = false;
|
||||
int paramCount = 0;
|
||||
|
||||
if (!isClinit && !isDInit) {
|
||||
if (descriptor && !descriptor->typeParameters.empty()) {
|
||||
std::vector<std::shared_ptr<VarType>> sharedBounds;
|
||||
std::vector<std::vector<std::shared_ptr<VarType>>> boundsVec;
|
||||
for (const auto& boundList : descriptor->typeParameterBounds) {
|
||||
std::vector<std::shared_ptr<VarType>> shared;
|
||||
for (const auto& b : boundList) {
|
||||
shared.push_back(std::make_shared<VarType>(b));
|
||||
}
|
||||
boundsVec.push_back(std::move(shared));
|
||||
}
|
||||
appendTypeParameters(buffer, descriptor->typeParameters, boundsVec);
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
if (!isInit) {
|
||||
if (descriptor) {
|
||||
buffer.append(ExprProcessor::getCastTypeName(descriptor->returnType));
|
||||
} else {
|
||||
buffer.append(ExprProcessor::getCastTypeName(md.ret));
|
||||
}
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(toValidJavaIdentifier(name));
|
||||
buffer.append('(');
|
||||
|
||||
int index = mt->hasModifier(CodeConstants::ACC_STATIC) ? 0 : 1;
|
||||
for (size_t i = 0; i < md.params.size(); ++i) {
|
||||
if (i > 0) buffer.append(", ");
|
||||
|
||||
VarType paramType = (descriptor && i < descriptor->parameterTypes.size())
|
||||
? descriptor->parameterTypes[i]
|
||||
: md.params[i];
|
||||
|
||||
bool isVarArg = (i == md.params.size() - 1) &&
|
||||
mt->hasModifier(CodeConstants::ACC_VARARGS) &&
|
||||
paramType.getArrayDim() > 0;
|
||||
if (isVarArg) {
|
||||
paramType = paramType.decreaseArrayDim();
|
||||
}
|
||||
|
||||
buffer.append(ExprProcessor::getCastTypeName(paramType));
|
||||
if (isVarArg) buffer.append("...");
|
||||
buffer.append(' ');
|
||||
|
||||
std::string paramName;
|
||||
if (methodWrapper->varProc) {
|
||||
paramName = methodWrapper->varProc->getVarName(VarVersion(index, 0));
|
||||
}
|
||||
if (paramName.empty()) {
|
||||
paramName = "param" + std::to_string(index);
|
||||
}
|
||||
buffer.append(paramName);
|
||||
|
||||
++paramCount;
|
||||
index += md.params[i].getStackSize();
|
||||
}
|
||||
|
||||
buffer.append(')');
|
||||
|
||||
auto* exceptAttr = static_cast<StructExceptionsAttribute*>(
|
||||
mt->getAttribute(StructGeneralAttribute::ATTRIBUTE_EXCEPTIONS_NAME));
|
||||
if (exceptAttr && !exceptAttr->getThrowsExceptions().empty()) {
|
||||
throwsExceptions = true;
|
||||
buffer.append(" throws ");
|
||||
const auto& excList = exceptAttr->getThrowsExceptions();
|
||||
for (size_t i = 0; i < excList.size(); ++i) {
|
||||
if (i > 0) buffer.append(", ");
|
||||
if (descriptor && i < descriptor->exceptionTypes.size()) {
|
||||
buffer.append(ExprProcessor::getCastTypeName(descriptor->exceptionTypes[i]));
|
||||
} else {
|
||||
ConstantPool* pool = cl->getPool();
|
||||
if (pool) {
|
||||
buffer.append(ExprProcessor::buildJavaClassName(
|
||||
exceptAttr->getExcClassname(static_cast<int>(i), *pool)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(void)throwsExceptions;
|
||||
}
|
||||
|
||||
if ((flags & (CodeConstants::ACC_ABSTRACT | CodeConstants::ACC_NATIVE)) != 0) {
|
||||
buffer.append(';').appendLineSeparator();
|
||||
} else {
|
||||
if (!isClinit && !isDInit) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
buffer.append('{').appendLineSeparator();
|
||||
|
||||
if (methodWrapper->root && !methodWrapper->decompiledWithErrors) {
|
||||
TextBuffer* code = methodWrapper->root->toJava(indent + 1, &BytecodeMappingTracer::DUMMY);
|
||||
if (code) {
|
||||
buffer.append(*code);
|
||||
delete code;
|
||||
}
|
||||
}
|
||||
|
||||
if (methodWrapper->decompiledWithErrors) {
|
||||
buffer.appendIndent(indent + 1)
|
||||
.append("// $FF: Couldn't be decompiled").appendLineSeparator();
|
||||
}
|
||||
|
||||
buffer.appendIndent(indent).append('}').appendLineSeparator();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// classToJava
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void ClassWriter::classToJava(ClassesProcessor::ClassNode* node, TextBuffer& buffer,
|
||||
int indent, ImportCollector* /*importCollector*/) {
|
||||
using ClassNode = ClassesProcessor::ClassNode;
|
||||
|
||||
if (!node) return;
|
||||
|
||||
ClassWrapper* wrapper = node->wrapper.get();
|
||||
if (!wrapper && node->type != ClassNode::CLASS_ANONYMOUS) return;
|
||||
|
||||
StructClass* cl = (wrapper ? wrapper->getClassStruct() : node->classStruct);
|
||||
|
||||
DecompilerContext::getLogger()->startWriteClass(cl->qualifiedName);
|
||||
|
||||
writeClassDefinition(node, buffer, indent);
|
||||
|
||||
bool hasContent = false;
|
||||
bool enumFields = false;
|
||||
|
||||
const auto* components = cl->getRecordComponents();
|
||||
|
||||
for (auto& fdPtr : cl->getFields()) {
|
||||
StructField* fd = fdPtr.get();
|
||||
|
||||
bool hide = (fd->isSynthetic() &&
|
||||
DecompilerContext::getOption(IFernflowerPreferences::REMOVE_SYNTHETIC)) ||
|
||||
(wrapper && wrapper->getHiddenMembers().count(
|
||||
InterpreterUtil::makeUniqueKey(fd->getName(), fd->getDescriptor())));
|
||||
if (hide) continue;
|
||||
|
||||
if (components != nullptr &&
|
||||
fd->getAccessFlags() == (CodeConstants::ACC_FINAL | CodeConstants::ACC_PRIVATE)) {
|
||||
bool isRecordField = false;
|
||||
for (const auto& comp : *components) {
|
||||
if (comp->getName() == fd->getName() &&
|
||||
comp->getDescriptor() == fd->getDescriptor()) {
|
||||
isRecordField = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isRecordField) continue;
|
||||
}
|
||||
|
||||
bool isEnumField = fd->hasModifier(CodeConstants::ACC_ENUM) &&
|
||||
DecompilerContext::getOption(IFernflowerPreferences::DECOMPILE_ENUM);
|
||||
if (isEnumField) {
|
||||
if (enumFields) {
|
||||
buffer.append(',').appendLineSeparator();
|
||||
}
|
||||
enumFields = true;
|
||||
} else if (enumFields) {
|
||||
buffer.append(';').appendLineSeparator();
|
||||
buffer.appendLineSeparator();
|
||||
enumFields = false;
|
||||
}
|
||||
|
||||
if (wrapper) {
|
||||
fieldToJava(wrapper, cl, fd, buffer, indent + 1);
|
||||
}
|
||||
hasContent = true;
|
||||
}
|
||||
|
||||
if (enumFields) {
|
||||
buffer.append(';').appendLineSeparator();
|
||||
}
|
||||
|
||||
for (auto& mtPtr : cl->getMethods()) {
|
||||
StructMethod* mt = mtPtr.get();
|
||||
|
||||
bool hide =
|
||||
(mt->isSynthetic() &&
|
||||
DecompilerContext::getOption(IFernflowerPreferences::REMOVE_SYNTHETIC)) ||
|
||||
(mt->hasModifier(CodeConstants::ACC_BRIDGE) &&
|
||||
DecompilerContext::getOption(IFernflowerPreferences::REMOVE_BRIDGE)) ||
|
||||
(wrapper && wrapper->getHiddenMembers().count(
|
||||
InterpreterUtil::makeUniqueKey(mt->getName(), mt->getDescriptor())));
|
||||
if (hide) continue;
|
||||
|
||||
int position = buffer.length();
|
||||
if (hasContent) {
|
||||
buffer.appendLineSeparator();
|
||||
}
|
||||
|
||||
bool written = methodToJava(node, mt, buffer, indent + 1);
|
||||
if (written) {
|
||||
hasContent = true;
|
||||
} else {
|
||||
buffer.setLength(position);
|
||||
}
|
||||
}
|
||||
|
||||
for (ClassNode* inner : node->nested) {
|
||||
if (inner->type != ClassNode::CLASS_MEMBER) continue;
|
||||
StructClass* innerCl = inner->classStruct;
|
||||
bool isSynthetic = (inner->accessFlags & CodeConstants::ACC_SYNTHETIC) != 0 ||
|
||||
innerCl->isSynthetic();
|
||||
bool hide = (isSynthetic &&
|
||||
DecompilerContext::getOption(IFernflowerPreferences::REMOVE_SYNTHETIC)) ||
|
||||
(wrapper && wrapper->getHiddenMembers().count(innerCl->qualifiedName));
|
||||
if (hide) continue;
|
||||
|
||||
if (hasContent) {
|
||||
buffer.appendLineSeparator();
|
||||
}
|
||||
classToJava(inner, buffer, indent + 1, nullptr);
|
||||
hasContent = true;
|
||||
}
|
||||
|
||||
buffer.appendIndent(indent).append('}');
|
||||
if (node->type != ClassNode::CLASS_ANONYMOUS) {
|
||||
buffer.appendLineSeparator();
|
||||
}
|
||||
|
||||
DecompilerContext::getLogger()->endWriteClass();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ClassesProcessor.h"
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class ImportCollector;
|
||||
class TextBuffer;
|
||||
|
||||
class ClassWriter {
|
||||
public:
|
||||
explicit ClassWriter(ClassesProcessor* classProcessor);
|
||||
|
||||
void classToJava(ClassesProcessor::ClassNode* node, TextBuffer& buffer,
|
||||
int indent, ImportCollector* importCollector);
|
||||
|
||||
static std::string getModifiers(int flags);
|
||||
|
||||
private:
|
||||
ClassesProcessor* classProcessor_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,344 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ClassesProcessor.h"
|
||||
#include "ClassWriter.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "IFernflowerLogger.h"
|
||||
#include "IFernflowerPreferences.h"
|
||||
#include "ImportCollector.h"
|
||||
#include "InterpreterUtil.h"
|
||||
#include "StructClass.h"
|
||||
#include "StructContext.h"
|
||||
#include "StructEnclosingMethodAttribute.h"
|
||||
#include "StructGeneralAttribute.h"
|
||||
#include "StructInnerClassesAttribute.h"
|
||||
#include "StructMethod.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "VarType.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ff {
|
||||
|
||||
ClassesProcessor::ClassNode::ClassNode(StructClass* cl)
|
||||
: classStruct(cl)
|
||||
, qualifiedName(cl->qualifiedName) {
|
||||
size_t lastSlash = cl->qualifiedName.rfind('/');
|
||||
simpleName = (lastSlash != std::string::npos)
|
||||
? cl->qualifiedName.substr(lastSlash + 1)
|
||||
: cl->qualifiedName;
|
||||
}
|
||||
|
||||
ClassesProcessor::ClassesProcessor(StructContext* context)
|
||||
: context_(context) {}
|
||||
|
||||
ClassesProcessor::~ClassesProcessor() {
|
||||
for (auto& [name, node] : mapRootClasses_) {
|
||||
delete node;
|
||||
}
|
||||
}
|
||||
|
||||
ClassesProcessor::ClassNode* ClassesProcessor::getMapRootClass(const std::string& name) const {
|
||||
auto it = mapRootClasses_.find(name);
|
||||
return (it != mapRootClasses_.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void ClassesProcessor::processClass(ClassNode* node) {
|
||||
if (!node || !node->wrapper) return;
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
"Decompiling class: " + node->qualifiedName, IFernflowerLogger::Severity::INFO);
|
||||
node->wrapper->init();
|
||||
}
|
||||
|
||||
static bool mustBeDecompiledFn(const std::unordered_set<std::string>& mustBeDecompiled,
|
||||
const std::string& cls) {
|
||||
if (mustBeDecompiled.empty()) return true;
|
||||
for (const auto& prefix : mustBeDecompiled) {
|
||||
if (cls.starts_with(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClassesProcessor::loadClasses(IMemberIdentifierRenamer* /*renamer*/) {
|
||||
bool bDecompileInner = DecompilerContext::getOption(IFernflowerPreferences::DECOMPILE_INNER);
|
||||
|
||||
struct Inner {
|
||||
std::string simpleName;
|
||||
int type = ClassNode::CLASS_MEMBER;
|
||||
int accessFlags = 0;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, Inner> mapInnerClasses;
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> mapNestedClassReferences;
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> mapEnclosingClassReferences;
|
||||
|
||||
for (const auto& [name, cl] : context_->getClasses()) {
|
||||
if (!cl->isOwn() || mapRootClasses_.count(cl->qualifiedName)) continue;
|
||||
|
||||
if (bDecompileInner) {
|
||||
StructGeneralAttribute* rawAttr =
|
||||
cl->getAttribute(StructGeneralAttribute::ATTRIBUTE_INNER_CLASSES_NAME);
|
||||
StructInnerClassesAttribute* innerAttr =
|
||||
static_cast<StructInnerClassesAttribute*>(rawAttr);
|
||||
|
||||
if (innerAttr) {
|
||||
for (const auto& entry : innerAttr->getEntries()) {
|
||||
const std::string& innerName = entry.innerName;
|
||||
if (innerName.empty()) continue;
|
||||
|
||||
Inner rec;
|
||||
rec.simpleName = entry.simpleName;
|
||||
rec.accessFlags = entry.accessFlags;
|
||||
|
||||
if (entry.simpleNameIdx == 0) {
|
||||
rec.type = ClassNode::CLASS_ANONYMOUS;
|
||||
} else if (entry.outerNameIdx == 0) {
|
||||
rec.type = ClassNode::CLASS_LOCAL;
|
||||
} else {
|
||||
StructClass* innerCl = context_->getClass(innerName);
|
||||
if (innerCl == nullptr) {
|
||||
rec.type = ClassNode::CLASS_MEMBER;
|
||||
} else {
|
||||
StructGeneralAttribute* rawEncAttr =
|
||||
innerCl->getAttribute(StructGeneralAttribute::ATTRIBUTE_ENCLOSING_METHOD_NAME);
|
||||
StructEnclosingMethodAttribute* encAttr =
|
||||
static_cast<StructEnclosingMethodAttribute*>(rawEncAttr);
|
||||
if (encAttr && !encAttr->getMethodName().empty()) {
|
||||
rec.type = ClassNode::CLASS_LOCAL;
|
||||
} else {
|
||||
rec.type = ClassNode::CLASS_MEMBER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string enclClassName = (entry.outerNameIdx != 0)
|
||||
? entry.enclosingName
|
||||
: cl->qualifiedName;
|
||||
|
||||
if (enclClassName.empty() || innerName == enclClassName) continue;
|
||||
if (rec.type == ClassNode::CLASS_MEMBER &&
|
||||
innerName != enclClassName + '$' + entry.simpleName)
|
||||
continue;
|
||||
|
||||
StructClass* enclosingClass = context_->getClass(enclClassName);
|
||||
if (enclosingClass && enclosingClass->isOwn()) {
|
||||
if (!mapInnerClasses.count(innerName)) {
|
||||
mapInnerClasses[innerName] = rec;
|
||||
}
|
||||
mapNestedClassReferences[enclClassName].insert(innerName);
|
||||
mapEnclosingClassReferences[innerName].insert(enclClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mustBeDecompiledFn(mustBeDecompiled_, cl->qualifiedName)) {
|
||||
ClassNode* node = new ClassNode(cl);
|
||||
node->type = ClassNode::CLASS_ROOT;
|
||||
node->accessFlags = cl->getAccessFlags();
|
||||
mapRootClasses_[cl->qualifiedName] = node;
|
||||
}
|
||||
|
||||
// Link enclosing methods
|
||||
{
|
||||
StructGeneralAttribute* rawEncAttr =
|
||||
cl->getAttribute(StructGeneralAttribute::ATTRIBUTE_ENCLOSING_METHOD_NAME);
|
||||
StructEnclosingMethodAttribute* encAttr =
|
||||
static_cast<StructEnclosingMethodAttribute*>(rawEncAttr);
|
||||
if (encAttr && !encAttr->getMethodName().empty()) {
|
||||
StructClass* parent = context_->getClass(encAttr->getClassName());
|
||||
if (parent) {
|
||||
StructMethod* method = parent->getMethod(encAttr->getMethodName(),
|
||||
encAttr->getMethodDescriptor());
|
||||
if (method) {
|
||||
method->enclosedClasses.insert(cl->qualifiedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bDecompileInner) {
|
||||
for (auto& [rootName, rootNode] : mapRootClasses_) {
|
||||
if (mapInnerClasses.count(rootName)) continue;
|
||||
|
||||
std::unordered_set<std::string> setVisited;
|
||||
std::list<std::string> stack;
|
||||
stack.push_back(rootName);
|
||||
setVisited.insert(rootName);
|
||||
|
||||
while (!stack.empty()) {
|
||||
std::string superClass = stack.front();
|
||||
stack.pop_front();
|
||||
|
||||
ClassNode* superNode = getMapRootClass(superClass);
|
||||
if (!superNode) continue;
|
||||
|
||||
auto nestedIt = mapNestedClassReferences.find(superClass);
|
||||
if (nestedIt == mapNestedClassReferences.end()) continue;
|
||||
|
||||
const std::unordered_set<std::string>& setNestedClasses = nestedIt->second;
|
||||
StructClass* scl = superNode->classStruct;
|
||||
|
||||
StructGeneralAttribute* rawInnerAttr =
|
||||
scl->getAttribute(StructGeneralAttribute::ATTRIBUTE_INNER_CLASSES_NAME);
|
||||
StructInnerClassesAttribute* innerAttr =
|
||||
static_cast<StructInnerClassesAttribute*>(rawInnerAttr);
|
||||
if (!innerAttr || innerAttr->getEntries().empty()) continue;
|
||||
|
||||
for (const auto& entry : innerAttr->getEntries()) {
|
||||
const std::string& nestedClass = entry.innerName;
|
||||
if (!setNestedClasses.count(nestedClass)) continue;
|
||||
if (!setVisited.insert(nestedClass).second) continue;
|
||||
|
||||
ClassNode* nestedNode = getMapRootClass(nestedClass);
|
||||
if (!nestedNode) continue;
|
||||
|
||||
auto recIt = mapInnerClasses.find(nestedClass);
|
||||
if (recIt == mapInnerClasses.end()) continue;
|
||||
const Inner& rec = recIt->second;
|
||||
|
||||
nestedNode->simpleName = rec.simpleName;
|
||||
nestedNode->type = rec.type;
|
||||
nestedNode->accessFlags = rec.accessFlags;
|
||||
|
||||
if (nestedNode->type == ClassNode::CLASS_ANONYMOUS) {
|
||||
nestedNode->accessFlags &= ~CodeConstants::ACC_STATIC;
|
||||
// anonymousClassType not in C++ ClassNode — skipped intentionally
|
||||
} else if (nestedNode->type == ClassNode::CLASS_LOCAL) {
|
||||
nestedNode->accessFlags &=
|
||||
(CodeConstants::ACC_ABSTRACT | CodeConstants::ACC_FINAL);
|
||||
}
|
||||
|
||||
superNode->nested.push_back(nestedNode);
|
||||
nestedNode->parent = superNode;
|
||||
|
||||
auto encIt = mapEnclosingClassReferences.find(nestedClass);
|
||||
if (encIt != mapEnclosingClassReferences.end()) {
|
||||
for (const auto& enc : encIt->second) {
|
||||
nestedNode->enclosingClasses.insert(enc);
|
||||
}
|
||||
}
|
||||
|
||||
stack.push_back(nestedClass);
|
||||
}
|
||||
|
||||
std::sort(superNode->nested.begin(), superNode->nested.end(),
|
||||
[](const ClassNode* a, const ClassNode* b) {
|
||||
return a->qualifiedName < b->qualifiedName;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [name, node] : mapRootClasses_) {
|
||||
processClass(node);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void initWrappers(ClassesProcessor::ClassNode* node) {
|
||||
if (node->type == ClassesProcessor::ClassNode::CLASS_LAMBDA) return;
|
||||
if (!node->wrapper) {
|
||||
node->wrapper = std::make_unique<ClassWrapper>(node->classStruct);
|
||||
}
|
||||
node->wrapper->init();
|
||||
for (ClassesProcessor::ClassNode* nd : node->nested) {
|
||||
initWrappers(nd);
|
||||
}
|
||||
}
|
||||
|
||||
static void addClassNameToImport(ClassesProcessor::ClassNode* node, ImportCollector& imp) {
|
||||
// Only register root classes — inner/member classes don't need their own import entry
|
||||
if (node->type == ClassesProcessor::ClassNode::CLASS_ROOT && !node->simpleName.empty()) {
|
||||
imp.getShortName(node->classStruct->qualifiedName, /*isClass=*/false);
|
||||
}
|
||||
for (ClassesProcessor::ClassNode* nd : node->nested) {
|
||||
addClassNameToImport(nd, imp);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroyWrappers(ClassesProcessor::ClassNode* node) {
|
||||
node->wrapper.reset();
|
||||
node->classStruct->releaseResources();
|
||||
for (auto& mPtr : node->classStruct->getMethods()) {
|
||||
mPtr->clearVariableNamer();
|
||||
}
|
||||
for (ClassesProcessor::ClassNode* nd : node->nested) {
|
||||
destroyWrappers(nd);
|
||||
}
|
||||
}
|
||||
|
||||
void ClassesProcessor::writeClass(ClassNode* node, TextBuffer& buffer) {
|
||||
if (!node || node->type != ClassNode::CLASS_ROOT) return;
|
||||
|
||||
StructClass* cl = node->classStruct;
|
||||
|
||||
bool packageInfo = cl->isSynthetic() && node->simpleName == "package-info";
|
||||
bool moduleInfo = cl->hasModifier(CodeConstants::ACC_MODULE) &&
|
||||
cl->hasAttribute(StructGeneralAttribute::ATTRIBUTE_MODULE_NAME);
|
||||
|
||||
DecompilerContext::getLogger()->startReadingClass(cl->qualifiedName);
|
||||
try {
|
||||
ImportCollector importCollector(cl->qualifiedName);
|
||||
DecompilerContext::startClass(&importCollector);
|
||||
|
||||
if (packageInfo) {
|
||||
size_t slash = cl->qualifiedName.rfind('/');
|
||||
if (slash != std::string::npos) {
|
||||
std::string packageName = cl->qualifiedName.substr(0, slash);
|
||||
for (char& c : packageName) if (c == '/') c = '.';
|
||||
buffer.append("package ").append(packageName).append(';')
|
||||
.appendLineSeparator().appendLineSeparator();
|
||||
}
|
||||
std::string importBuf;
|
||||
importCollector.writeImports(importBuf, false);
|
||||
buffer.append(importBuf);
|
||||
} else if (moduleInfo) {
|
||||
// TODO: full module-info output
|
||||
std::string importBuf;
|
||||
importCollector.writeImports(importBuf, true);
|
||||
buffer.append(importBuf);
|
||||
} else {
|
||||
addClassNameToImport(node, importCollector);
|
||||
initWrappers(node);
|
||||
|
||||
TextBuffer classBuffer(ClassesProcessor::AVERAGE_CLASS_SIZE);
|
||||
ClassWriter writer(this);
|
||||
writer.classToJava(node, classBuffer, 0, &importCollector);
|
||||
|
||||
size_t slash = cl->qualifiedName.rfind('/');
|
||||
if (slash != std::string::npos) {
|
||||
std::string packageName = cl->qualifiedName.substr(0, slash);
|
||||
for (char& c : packageName) if (c == '/') c = '.';
|
||||
buffer.append("package ").append(packageName).append(';')
|
||||
.appendLineSeparator().appendLineSeparator();
|
||||
}
|
||||
|
||||
std::string importBuf;
|
||||
importCollector.writeImports(importBuf, true);
|
||||
buffer.append(importBuf);
|
||||
buffer.append(classBuffer);
|
||||
}
|
||||
} catch (...) {
|
||||
destroyWrappers(node);
|
||||
DecompilerContext::getLogger()->endReadingClass();
|
||||
throw;
|
||||
}
|
||||
destroyWrappers(node);
|
||||
DecompilerContext::getLogger()->endReadingClass();
|
||||
}
|
||||
|
||||
void ClassesProcessor::processInnerClasses(ClassNode* node) {
|
||||
(void)node;
|
||||
}
|
||||
|
||||
void ClassesProcessor::addToMustBeDecompiled(const std::string& prefix) {
|
||||
mustBeDecompiled_.insert(prefix);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "VBStyleCollection.h"
|
||||
#include "ClassWrapper.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class IMemberIdentifierRenamer;
|
||||
class StructClass;
|
||||
class StructContext;
|
||||
class StructMethod;
|
||||
class TextBuffer;
|
||||
|
||||
class ClassesProcessor {
|
||||
public:
|
||||
static constexpr int AVERAGE_CLASS_SIZE = 16 * 1024;
|
||||
|
||||
struct ClassNode {
|
||||
static constexpr int CLASS_ROOT = 0;
|
||||
static constexpr int CLASS_MEMBER = 1;
|
||||
static constexpr int CLASS_ANONYMOUS = 2;
|
||||
static constexpr int CLASS_LOCAL = 3;
|
||||
static constexpr int CLASS_LAMBDA = 4;
|
||||
|
||||
std::string qualifiedName;
|
||||
int type = CLASS_ROOT;
|
||||
int accessFlags = 0;
|
||||
std::string simpleName;
|
||||
std::string enclosingMethod;
|
||||
|
||||
StructClass* classStruct = nullptr;
|
||||
std::unique_ptr<ClassWrapper> wrapper;
|
||||
ClassNode* parent = nullptr;
|
||||
std::vector<ClassNode*> nested;
|
||||
std::unordered_set<std::string> enclosingClasses;
|
||||
|
||||
explicit ClassNode(StructClass* cl);
|
||||
};
|
||||
|
||||
explicit ClassesProcessor(StructContext* context);
|
||||
~ClassesProcessor();
|
||||
|
||||
void processClass(ClassNode* node);
|
||||
void writeClass(ClassNode* node, TextBuffer& buffer);
|
||||
|
||||
void loadClasses(IMemberIdentifierRenamer* renamer);
|
||||
void addToMustBeDecompiled(const std::string& prefix);
|
||||
|
||||
ClassNode* getMapRootClass(const std::string& name) const;
|
||||
const std::unordered_map<std::string, ClassNode*>& getMapRootClasses() const { return mapRootClasses_; }
|
||||
|
||||
private:
|
||||
void buildRootClasses();
|
||||
void processInnerClasses(ClassNode* node);
|
||||
|
||||
StructContext* context_;
|
||||
std::unordered_map<std::string, ClassNode*> mapRootClasses_;
|
||||
std::unordered_set<std::string> mustBeDecompiled_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ClasspathHelper.h"
|
||||
#include "MethodDescriptor.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
void* ClasspathHelper::findMethod(const std::string& classname, const std::string& methodName,
|
||||
const MethodDescriptor& descriptor) {
|
||||
// C++ has no Java-style runtime reflection; return null stub
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class MethodDescriptor;
|
||||
|
||||
class ClasspathHelper {
|
||||
public:
|
||||
// Returns nullptr (Java's null Method) — C++ doesn't have runtime reflection
|
||||
static void* findMethod(const std::string& classname, const std::string& methodName,
|
||||
const MethodDescriptor& descriptor);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ClasspathScanner.h"
|
||||
#include "StructContext.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "IFernflowerLogger.h"
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ff {
|
||||
|
||||
static std::vector<std::string> splitPath(const std::string& path) {
|
||||
std::vector<std::string> parts;
|
||||
if (path.empty()) return parts;
|
||||
|
||||
#ifdef _WIN32
|
||||
char sep = ';';
|
||||
#else
|
||||
char sep = ':';
|
||||
#endif
|
||||
|
||||
std::istringstream ss(path);
|
||||
std::string tok;
|
||||
while (std::getline(ss, tok, sep)) {
|
||||
if (!tok.empty()) parts.push_back(tok);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
void ClasspathScanner::addAllClasspath(StructContext& ctx) {
|
||||
std::unordered_set<std::string> found;
|
||||
|
||||
const char* props[] = { std::getenv("CLASSPATH") };
|
||||
|
||||
for (const char* prop : props) {
|
||||
if (!prop) continue;
|
||||
for (const std::string& path : splitPath(prop)) {
|
||||
std::filesystem::path fspath(path);
|
||||
std::error_code ec;
|
||||
bool exists = std::filesystem::exists(fspath, ec);
|
||||
if (!exists || ec) continue;
|
||||
|
||||
std::string abs = std::filesystem::absolute(fspath, ec).string();
|
||||
if (ec || found.count(abs)) continue;
|
||||
|
||||
std::string name = fspath.filename().string();
|
||||
bool isClass = name.size() >= 6 && name.substr(name.size() - 6) == ".class";
|
||||
bool isJar = name.size() >= 4 && name.substr(name.size() - 4) == ".jar";
|
||||
|
||||
if (isClass || isJar) {
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
"Adding File to context from classpath: " + path,
|
||||
IFernflowerLogger::Severity::INFO);
|
||||
ctx.addSpace(path, false);
|
||||
found.insert(abs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addAllModulePath(ctx);
|
||||
}
|
||||
|
||||
void ClasspathScanner::addAllModulePath(StructContext& ctx) {
|
||||
// Module system not available outside JVM; no-op in C++ port
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
namespace ff {
|
||||
|
||||
class StructContext;
|
||||
|
||||
class ClasspathScanner {
|
||||
public:
|
||||
static void addAllClasspath(StructContext& ctx);
|
||||
|
||||
private:
|
||||
static void addAllModulePath(StructContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,361 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
struct CodeConstants {
|
||||
// ----------------------------------------------------------------------
|
||||
// BYTECODE VERSIONS
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t BYTECODE_JAVA_LE_4 = 48;
|
||||
static constexpr int32_t BYTECODE_JAVA_5 = 49;
|
||||
static constexpr int32_t BYTECODE_JAVA_6 = 50;
|
||||
static constexpr int32_t BYTECODE_JAVA_7 = 51;
|
||||
static constexpr int32_t BYTECODE_JAVA_8 = 52;
|
||||
static constexpr int32_t BYTECODE_JAVA_9 = 53;
|
||||
static constexpr int32_t BYTECODE_JAVA_10 = 54;
|
||||
static constexpr int32_t BYTECODE_JAVA_11 = 55;
|
||||
static constexpr int32_t BYTECODE_JAVA_12 = 56;
|
||||
static constexpr int32_t BYTECODE_JAVA_13 = 57;
|
||||
static constexpr int32_t BYTECODE_JAVA_14 = 58;
|
||||
static constexpr int32_t BYTECODE_JAVA_15 = 59;
|
||||
static constexpr int32_t BYTECODE_JAVA_16 = 60;
|
||||
static constexpr int32_t BYTECODE_JAVA_17 = 61;
|
||||
static constexpr int32_t BYTECODE_JAVA_18 = 62;
|
||||
static constexpr int32_t BYTECODE_JAVA_19 = 63;
|
||||
static constexpr int32_t BYTECODE_JAVA_20 = 64;
|
||||
static constexpr int32_t BYTECODE_JAVA_21 = 65;
|
||||
static constexpr int32_t BYTECODE_JAVA_22 = 66;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// VARIABLE TYPES
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t TYPE_BYTE = 0;
|
||||
static constexpr int32_t TYPE_CHAR = 1;
|
||||
static constexpr int32_t TYPE_DOUBLE = 2;
|
||||
static constexpr int32_t TYPE_FLOAT = 3;
|
||||
static constexpr int32_t TYPE_INT = 4;
|
||||
static constexpr int32_t TYPE_LONG = 5;
|
||||
static constexpr int32_t TYPE_SHORT = 6;
|
||||
static constexpr int32_t TYPE_BOOLEAN = 7;
|
||||
static constexpr int32_t TYPE_OBJECT = 8;
|
||||
static constexpr int32_t TYPE_ADDRESS = 9;
|
||||
static constexpr int32_t TYPE_VOID = 10;
|
||||
static constexpr int32_t TYPE_ANY = 11;
|
||||
static constexpr int32_t TYPE_GROUP2EMPTY = 12;
|
||||
static constexpr int32_t TYPE_NULL = 13;
|
||||
static constexpr int32_t TYPE_NOTINITIALIZED = 14;
|
||||
static constexpr int32_t TYPE_BYTECHAR = 15;
|
||||
static constexpr int32_t TYPE_SHORTCHAR = 16;
|
||||
static constexpr int32_t TYPE_UNKNOWN = 17;
|
||||
static constexpr int32_t TYPE_GENVAR = 18;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// VARIABLE TYPE FAMILIES
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t TYPE_FAMILY_UNKNOWN = 0;
|
||||
static constexpr int32_t TYPE_FAMILY_BOOLEAN = 1;
|
||||
static constexpr int32_t TYPE_FAMILY_INTEGER = 2;
|
||||
static constexpr int32_t TYPE_FAMILY_FLOAT = 3;
|
||||
static constexpr int32_t TYPE_FAMILY_LONG = 4;
|
||||
static constexpr int32_t TYPE_FAMILY_DOUBLE = 5;
|
||||
static constexpr int32_t TYPE_FAMILY_OBJECT = 6;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ACCESS FLAGS
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t ACC_PUBLIC = 0x0001;
|
||||
static constexpr int32_t ACC_PRIVATE = 0x0002;
|
||||
static constexpr int32_t ACC_PROTECTED = 0x0004;
|
||||
static constexpr int32_t ACC_STATIC = 0x0008;
|
||||
static constexpr int32_t ACC_FINAL = 0x0010;
|
||||
static constexpr int32_t ACC_SYNCHRONIZED = 0x0020;
|
||||
static constexpr int32_t ACC_OPEN = 0x0020;
|
||||
static constexpr int32_t ACC_TRANSITIVE = 0x0020;
|
||||
static constexpr int32_t ACC_VOLATILE = 0x0040;
|
||||
static constexpr int32_t ACC_BRIDGE = 0x0040;
|
||||
static constexpr int32_t ACC_STATIC_PHASE = 0x0040;
|
||||
static constexpr int32_t ACC_TRANSIENT = 0x0080;
|
||||
static constexpr int32_t ACC_VARARGS = 0x0080;
|
||||
static constexpr int32_t ACC_NATIVE = 0x0100;
|
||||
static constexpr int32_t ACC_ABSTRACT = 0x0400;
|
||||
static constexpr int32_t ACC_STRICT = 0x0800;
|
||||
static constexpr int32_t ACC_SYNTHETIC = 0x1000;
|
||||
static constexpr int32_t ACC_ANNOTATION = 0x2000;
|
||||
static constexpr int32_t ACC_ENUM = 0x4000;
|
||||
static constexpr int32_t ACC_MANDATED = 0x8000;
|
||||
static constexpr int32_t ACC_MODULE = 0x8000;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// CLASS FLAGS
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t ACC_SUPER = 0x0020;
|
||||
static constexpr int32_t ACC_INTERFACE = 0x0200;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// INSTRUCTION GROUPS
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t GROUP_GENERAL = 1;
|
||||
static constexpr int32_t GROUP_JUMP = 2;
|
||||
static constexpr int32_t GROUP_SWITCH = 3;
|
||||
static constexpr int32_t GROUP_INVOCATION = 4;
|
||||
static constexpr int32_t GROUP_FIELDACCESS = 5;
|
||||
static constexpr int32_t GROUP_RETURN = 6;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// POOL CONSTANTS
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t CONSTANT_Utf8 = 1;
|
||||
static constexpr int32_t CONSTANT_Integer = 3;
|
||||
static constexpr int32_t CONSTANT_Float = 4;
|
||||
static constexpr int32_t CONSTANT_Long = 5;
|
||||
static constexpr int32_t CONSTANT_Double = 6;
|
||||
static constexpr int32_t CONSTANT_Class = 7;
|
||||
static constexpr int32_t CONSTANT_String = 8;
|
||||
static constexpr int32_t CONSTANT_Fieldref = 9;
|
||||
static constexpr int32_t CONSTANT_Methodref = 10;
|
||||
static constexpr int32_t CONSTANT_InterfaceMethodref = 11;
|
||||
static constexpr int32_t CONSTANT_NameAndType = 12;
|
||||
static constexpr int32_t CONSTANT_MethodHandle = 15;
|
||||
static constexpr int32_t CONSTANT_MethodType = 16;
|
||||
static constexpr int32_t CONSTANT_Dynamic = 17;
|
||||
static constexpr int32_t CONSTANT_InvokeDynamic = 18;
|
||||
static constexpr int32_t CONSTANT_Module = 19;
|
||||
static constexpr int32_t CONSTANT_Package = 20;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// MethodHandle reference_kind values
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_getField = 1;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_getStatic = 2;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_putField = 3;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_putStatic = 4;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_invokeVirtual = 5;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_invokeStatic = 6;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_invokeSpecial = 7;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_newInvokeSpecial = 8;
|
||||
static constexpr int32_t CONSTANT_MethodHandle_REF_invokeInterface = 9;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// VM OPCODES
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
static constexpr int32_t opc_nop = 0;
|
||||
static constexpr int32_t opc_aconst_null = 1;
|
||||
static constexpr int32_t opc_iconst_m1 = 2;
|
||||
static constexpr int32_t opc_iconst_0 = 3;
|
||||
static constexpr int32_t opc_iconst_1 = 4;
|
||||
static constexpr int32_t opc_iconst_2 = 5;
|
||||
static constexpr int32_t opc_iconst_3 = 6;
|
||||
static constexpr int32_t opc_iconst_4 = 7;
|
||||
static constexpr int32_t opc_iconst_5 = 8;
|
||||
static constexpr int32_t opc_lconst_0 = 9;
|
||||
static constexpr int32_t opc_lconst_1 = 10;
|
||||
static constexpr int32_t opc_fconst_0 = 11;
|
||||
static constexpr int32_t opc_fconst_1 = 12;
|
||||
static constexpr int32_t opc_fconst_2 = 13;
|
||||
static constexpr int32_t opc_dconst_0 = 14;
|
||||
static constexpr int32_t opc_dconst_1 = 15;
|
||||
static constexpr int32_t opc_bipush = 16;
|
||||
static constexpr int32_t opc_sipush = 17;
|
||||
static constexpr int32_t opc_ldc = 18;
|
||||
static constexpr int32_t opc_ldc_w = 19;
|
||||
static constexpr int32_t opc_ldc2_w = 20;
|
||||
static constexpr int32_t opc_iload = 21;
|
||||
static constexpr int32_t opc_lload = 22;
|
||||
static constexpr int32_t opc_fload = 23;
|
||||
static constexpr int32_t opc_dload = 24;
|
||||
static constexpr int32_t opc_aload = 25;
|
||||
static constexpr int32_t opc_iload_0 = 26;
|
||||
static constexpr int32_t opc_iload_1 = 27;
|
||||
static constexpr int32_t opc_iload_2 = 28;
|
||||
static constexpr int32_t opc_iload_3 = 29;
|
||||
static constexpr int32_t opc_lload_0 = 30;
|
||||
static constexpr int32_t opc_lload_1 = 31;
|
||||
static constexpr int32_t opc_lload_2 = 32;
|
||||
static constexpr int32_t opc_lload_3 = 33;
|
||||
static constexpr int32_t opc_fload_0 = 34;
|
||||
static constexpr int32_t opc_fload_1 = 35;
|
||||
static constexpr int32_t opc_fload_2 = 36;
|
||||
static constexpr int32_t opc_fload_3 = 37;
|
||||
static constexpr int32_t opc_dload_0 = 38;
|
||||
static constexpr int32_t opc_dload_1 = 39;
|
||||
static constexpr int32_t opc_dload_2 = 40;
|
||||
static constexpr int32_t opc_dload_3 = 41;
|
||||
static constexpr int32_t opc_aload_0 = 42;
|
||||
static constexpr int32_t opc_aload_1 = 43;
|
||||
static constexpr int32_t opc_aload_2 = 44;
|
||||
static constexpr int32_t opc_aload_3 = 45;
|
||||
static constexpr int32_t opc_iaload = 46;
|
||||
static constexpr int32_t opc_laload = 47;
|
||||
static constexpr int32_t opc_faload = 48;
|
||||
static constexpr int32_t opc_daload = 49;
|
||||
static constexpr int32_t opc_aaload = 50;
|
||||
static constexpr int32_t opc_baload = 51;
|
||||
static constexpr int32_t opc_caload = 52;
|
||||
static constexpr int32_t opc_saload = 53;
|
||||
static constexpr int32_t opc_istore = 54;
|
||||
static constexpr int32_t opc_lstore = 55;
|
||||
static constexpr int32_t opc_fstore = 56;
|
||||
static constexpr int32_t opc_dstore = 57;
|
||||
static constexpr int32_t opc_astore = 58;
|
||||
static constexpr int32_t opc_istore_0 = 59;
|
||||
static constexpr int32_t opc_istore_1 = 60;
|
||||
static constexpr int32_t opc_istore_2 = 61;
|
||||
static constexpr int32_t opc_istore_3 = 62;
|
||||
static constexpr int32_t opc_lstore_0 = 63;
|
||||
static constexpr int32_t opc_lstore_1 = 64;
|
||||
static constexpr int32_t opc_lstore_2 = 65;
|
||||
static constexpr int32_t opc_lstore_3 = 66;
|
||||
static constexpr int32_t opc_fstore_0 = 67;
|
||||
static constexpr int32_t opc_fstore_1 = 68;
|
||||
static constexpr int32_t opc_fstore_2 = 69;
|
||||
static constexpr int32_t opc_fstore_3 = 70;
|
||||
static constexpr int32_t opc_dstore_0 = 71;
|
||||
static constexpr int32_t opc_dstore_1 = 72;
|
||||
static constexpr int32_t opc_dstore_2 = 73;
|
||||
static constexpr int32_t opc_dstore_3 = 74;
|
||||
static constexpr int32_t opc_astore_0 = 75;
|
||||
static constexpr int32_t opc_astore_1 = 76;
|
||||
static constexpr int32_t opc_astore_2 = 77;
|
||||
static constexpr int32_t opc_astore_3 = 78;
|
||||
static constexpr int32_t opc_iastore = 79;
|
||||
static constexpr int32_t opc_lastore = 80;
|
||||
static constexpr int32_t opc_fastore = 81;
|
||||
static constexpr int32_t opc_dastore = 82;
|
||||
static constexpr int32_t opc_aastore = 83;
|
||||
static constexpr int32_t opc_bastore = 84;
|
||||
static constexpr int32_t opc_castore = 85;
|
||||
static constexpr int32_t opc_sastore = 86;
|
||||
static constexpr int32_t opc_pop = 87;
|
||||
static constexpr int32_t opc_pop2 = 88;
|
||||
static constexpr int32_t opc_dup = 89;
|
||||
static constexpr int32_t opc_dup_x1 = 90;
|
||||
static constexpr int32_t opc_dup_x2 = 91;
|
||||
static constexpr int32_t opc_dup2 = 92;
|
||||
static constexpr int32_t opc_dup2_x1 = 93;
|
||||
static constexpr int32_t opc_dup2_x2 = 94;
|
||||
static constexpr int32_t opc_swap = 95;
|
||||
static constexpr int32_t opc_iadd = 96;
|
||||
static constexpr int32_t opc_ladd = 97;
|
||||
static constexpr int32_t opc_fadd = 98;
|
||||
static constexpr int32_t opc_dadd = 99;
|
||||
static constexpr int32_t opc_isub = 100;
|
||||
static constexpr int32_t opc_lsub = 101;
|
||||
static constexpr int32_t opc_fsub = 102;
|
||||
static constexpr int32_t opc_dsub = 103;
|
||||
static constexpr int32_t opc_imul = 104;
|
||||
static constexpr int32_t opc_lmul = 105;
|
||||
static constexpr int32_t opc_fmul = 106;
|
||||
static constexpr int32_t opc_dmul = 107;
|
||||
static constexpr int32_t opc_idiv = 108;
|
||||
static constexpr int32_t opc_ldiv = 109;
|
||||
static constexpr int32_t opc_fdiv = 110;
|
||||
static constexpr int32_t opc_ddiv = 111;
|
||||
static constexpr int32_t opc_irem = 112;
|
||||
static constexpr int32_t opc_lrem = 113;
|
||||
static constexpr int32_t opc_frem = 114;
|
||||
static constexpr int32_t opc_drem = 115;
|
||||
static constexpr int32_t opc_ineg = 116;
|
||||
static constexpr int32_t opc_lneg = 117;
|
||||
static constexpr int32_t opc_fneg = 118;
|
||||
static constexpr int32_t opc_dneg = 119;
|
||||
static constexpr int32_t opc_ishl = 120;
|
||||
static constexpr int32_t opc_lshl = 121;
|
||||
static constexpr int32_t opc_ishr = 122;
|
||||
static constexpr int32_t opc_lshr = 123;
|
||||
static constexpr int32_t opc_iushr = 124;
|
||||
static constexpr int32_t opc_lushr = 125;
|
||||
static constexpr int32_t opc_iand = 126;
|
||||
static constexpr int32_t opc_land = 127;
|
||||
static constexpr int32_t opc_ior = 128;
|
||||
static constexpr int32_t opc_lor = 129;
|
||||
static constexpr int32_t opc_ixor = 130;
|
||||
static constexpr int32_t opc_lxor = 131;
|
||||
static constexpr int32_t opc_iinc = 132;
|
||||
static constexpr int32_t opc_i2l = 133;
|
||||
static constexpr int32_t opc_i2f = 134;
|
||||
static constexpr int32_t opc_i2d = 135;
|
||||
static constexpr int32_t opc_l2i = 136;
|
||||
static constexpr int32_t opc_l2f = 137;
|
||||
static constexpr int32_t opc_l2d = 138;
|
||||
static constexpr int32_t opc_f2i = 139;
|
||||
static constexpr int32_t opc_f2l = 140;
|
||||
static constexpr int32_t opc_f2d = 141;
|
||||
static constexpr int32_t opc_d2i = 142;
|
||||
static constexpr int32_t opc_d2l = 143;
|
||||
static constexpr int32_t opc_d2f = 144;
|
||||
static constexpr int32_t opc_i2b = 145;
|
||||
static constexpr int32_t opc_i2c = 146;
|
||||
static constexpr int32_t opc_i2s = 147;
|
||||
static constexpr int32_t opc_lcmp = 148;
|
||||
static constexpr int32_t opc_fcmpl = 149;
|
||||
static constexpr int32_t opc_fcmpg = 150;
|
||||
static constexpr int32_t opc_dcmpl = 151;
|
||||
static constexpr int32_t opc_dcmpg = 152;
|
||||
static constexpr int32_t opc_ifeq = 153;
|
||||
static constexpr int32_t opc_ifne = 154;
|
||||
static constexpr int32_t opc_iflt = 155;
|
||||
static constexpr int32_t opc_ifge = 156;
|
||||
static constexpr int32_t opc_ifgt = 157;
|
||||
static constexpr int32_t opc_ifle = 158;
|
||||
static constexpr int32_t opc_if_icmpeq = 159;
|
||||
static constexpr int32_t opc_if_icmpne = 160;
|
||||
static constexpr int32_t opc_if_icmplt = 161;
|
||||
static constexpr int32_t opc_if_icmpge = 162;
|
||||
static constexpr int32_t opc_if_icmpgt = 163;
|
||||
static constexpr int32_t opc_if_icmple = 164;
|
||||
static constexpr int32_t opc_if_acmpeq = 165;
|
||||
static constexpr int32_t opc_if_acmpne = 166;
|
||||
static constexpr int32_t opc_goto = 167;
|
||||
static constexpr int32_t opc_jsr = 168;
|
||||
static constexpr int32_t opc_ret = 169;
|
||||
static constexpr int32_t opc_tableswitch = 170;
|
||||
static constexpr int32_t opc_lookupswitch = 171;
|
||||
static constexpr int32_t opc_ireturn = 172;
|
||||
static constexpr int32_t opc_lreturn = 173;
|
||||
static constexpr int32_t opc_freturn = 174;
|
||||
static constexpr int32_t opc_dreturn = 175;
|
||||
static constexpr int32_t opc_areturn = 176;
|
||||
static constexpr int32_t opc_return = 177;
|
||||
static constexpr int32_t opc_getstatic = 178;
|
||||
static constexpr int32_t opc_putstatic = 179;
|
||||
static constexpr int32_t opc_getfield = 180;
|
||||
static constexpr int32_t opc_putfield = 181;
|
||||
static constexpr int32_t opc_invokevirtual = 182;
|
||||
static constexpr int32_t opc_invokespecial = 183;
|
||||
static constexpr int32_t opc_invokestatic = 184;
|
||||
static constexpr int32_t opc_invokeinterface = 185;
|
||||
static constexpr int32_t opc_invokedynamic = 186;
|
||||
static constexpr int32_t opc_new = 187;
|
||||
static constexpr int32_t opc_newarray = 188;
|
||||
static constexpr int32_t opc_anewarray = 189;
|
||||
static constexpr int32_t opc_arraylength = 190;
|
||||
static constexpr int32_t opc_athrow = 191;
|
||||
static constexpr int32_t opc_checkcast = 192;
|
||||
static constexpr int32_t opc_instanceof = 193;
|
||||
static constexpr int32_t opc_monitorenter = 194;
|
||||
static constexpr int32_t opc_monitorexit = 195;
|
||||
static constexpr int32_t opc_wide = 196;
|
||||
static constexpr int32_t opc_multianewarray = 197;
|
||||
static constexpr int32_t opc_ifnull = 198;
|
||||
static constexpr int32_t opc_ifnonnull = 199;
|
||||
static constexpr int32_t opc_goto_w = 200;
|
||||
static constexpr int32_t opc_jsr_w = 201;
|
||||
|
||||
// Special method names
|
||||
static inline const std::string CLINIT_NAME = "<clinit>";
|
||||
static inline const std::string INIT_NAME = "<init>";
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ConstExprent.h"
|
||||
#include "CheckTypesResult.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "MatchNode.h"
|
||||
#include "MatchEngine.h"
|
||||
#include "IMatchable.h"
|
||||
#include "CodeConstants.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace ff {
|
||||
|
||||
VarType ConstExprent::guessType(int32_t val, bool boolPermitted) {
|
||||
if (boolPermitted && (val == 0 || val == 1)) return VarType::VARTYPE_BOOLEAN;
|
||||
if (val >= -128 && val <= 127) return VarType::VARTYPE_BYTE;
|
||||
if (val >= -32768 && val <= 32767) return VarType::VARTYPE_SHORT;
|
||||
return VarType::VARTYPE_INT;
|
||||
}
|
||||
|
||||
ConstExprent::ConstExprent(int32_t val, bool boolPermitted, uint64_t bytecodeOffsets)
|
||||
: Exprent(EXPRENT_CONST)
|
||||
, constType_(guessType(val, boolPermitted))
|
||||
, value_(val)
|
||||
, boolPermitted_(boolPermitted)
|
||||
{
|
||||
bytecode = bytecodeOffsets;
|
||||
}
|
||||
|
||||
ConstExprent::ConstExprent(VarType constType, ConstValue value, uint64_t bytecodeOffsets)
|
||||
: Exprent(EXPRENT_CONST)
|
||||
, constType_(std::move(constType))
|
||||
, value_(std::move(value))
|
||||
{
|
||||
bytecode = bytecodeOffsets;
|
||||
}
|
||||
|
||||
ConstExprent::ConstExprent(VarType constType, ConstValue value, uint64_t bytecodeOffsets,
|
||||
StructMember* parent)
|
||||
: Exprent(EXPRENT_CONST)
|
||||
, constType_(std::move(constType))
|
||||
, value_(std::move(value))
|
||||
, parent_(parent)
|
||||
{
|
||||
bytecode = bytecodeOffsets;
|
||||
}
|
||||
|
||||
bool ConstExprent::isNull() const {
|
||||
return std::holds_alternative<std::monostate>(value_);
|
||||
}
|
||||
|
||||
int32_t ConstExprent::getIntValue() const {
|
||||
return std::get<int32_t>(value_);
|
||||
}
|
||||
|
||||
int64_t ConstExprent::getLongValue() const {
|
||||
return std::get<int64_t>(value_);
|
||||
}
|
||||
|
||||
float ConstExprent::getFloatValue() const {
|
||||
return std::get<float>(value_);
|
||||
}
|
||||
|
||||
double ConstExprent::getDoubleValue() const {
|
||||
return std::get<double>(value_);
|
||||
}
|
||||
|
||||
CheckTypesResult* ConstExprent::checkExprTypeBounds() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Exprent* ConstExprent::copy() const {
|
||||
ConstExprent* ce = new ConstExprent(constType_, value_, bytecode);
|
||||
ce->boolPermitted_ = boolPermitted_;
|
||||
ce->parent_ = parent_;
|
||||
return ce;
|
||||
}
|
||||
|
||||
TextBuffer* ConstExprent::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
if (tracer) tracer->addMapping(bytecode);
|
||||
auto* buf = new TextBuffer();
|
||||
|
||||
if (isNull()) {
|
||||
buf->append("null");
|
||||
return buf;
|
||||
}
|
||||
|
||||
int t = constType_.getType();
|
||||
if (t == CodeConstants::TYPE_BOOLEAN) {
|
||||
bool bv = (std::holds_alternative<int32_t>(value_) && std::get<int32_t>(value_) != 0);
|
||||
buf->append(bv ? "true" : "false");
|
||||
} else if (t == CodeConstants::TYPE_INT || t == CodeConstants::TYPE_BYTE ||
|
||||
t == CodeConstants::TYPE_CHAR || t == CodeConstants::TYPE_SHORT) {
|
||||
if (std::holds_alternative<int32_t>(value_)) {
|
||||
if (t == CodeConstants::TYPE_CHAR) {
|
||||
int32_t v = std::get<int32_t>(value_);
|
||||
if (v >= 32 && v < 127) {
|
||||
buf->append("'");
|
||||
buf->append(std::string(1, (char)v));
|
||||
buf->append("'");
|
||||
} else {
|
||||
buf->append("'\\u");
|
||||
std::ostringstream ss;
|
||||
ss << std::hex << v;
|
||||
buf->append(ss.str());
|
||||
buf->append("'");
|
||||
}
|
||||
} else {
|
||||
buf->append(std::to_string(std::get<int32_t>(value_)));
|
||||
}
|
||||
}
|
||||
} else if (t == CodeConstants::TYPE_LONG) {
|
||||
if (std::holds_alternative<int64_t>(value_)) {
|
||||
buf->append(std::to_string(std::get<int64_t>(value_)));
|
||||
buf->append('L');
|
||||
}
|
||||
} else if (t == CodeConstants::TYPE_FLOAT) {
|
||||
if (std::holds_alternative<float>(value_)) {
|
||||
buf->append(std::to_string(std::get<float>(value_)));
|
||||
buf->append('F');
|
||||
}
|
||||
} else if (t == CodeConstants::TYPE_DOUBLE) {
|
||||
if (std::holds_alternative<double>(value_)) {
|
||||
buf->append(std::to_string(std::get<double>(value_)));
|
||||
}
|
||||
} else if (t == CodeConstants::TYPE_OBJECT) {
|
||||
if (std::holds_alternative<std::string>(value_)) {
|
||||
buf->append("\"");
|
||||
buf->append(std::get<std::string>(value_));
|
||||
buf->append("\"");
|
||||
}
|
||||
} else {
|
||||
buf->append("null");
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
void ConstExprent::adjustConstType(const VarType& expectedType) {
|
||||
// Type coercion hint
|
||||
if (expectedType == VarType::VARTYPE_BOOLEAN && boolPermitted_) {
|
||||
constType_ = VarType::VARTYPE_BOOLEAN;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConstExprent::equals(const Exprent* o) const {
|
||||
if (o == this) return true;
|
||||
auto* cn = dynamic_cast<const ConstExprent*>(o);
|
||||
if (!cn) return false;
|
||||
return constType_ == cn->constType_ && value_ == cn->value_;
|
||||
}
|
||||
|
||||
void ConstExprent::fillBytecodeRange(uint64_t* values) const {
|
||||
measureBytecode(values);
|
||||
}
|
||||
|
||||
bool ConstExprent::match(MatchNode* matchNode, MatchEngine* engine) {
|
||||
for (auto& [key, rule] : matchNode->getRules()) {
|
||||
if (key == IMatchable::MatchProperties::EXPRENT_CONSTTYPE) {
|
||||
// Compare type
|
||||
} else if (key == IMatchable::MatchProperties::EXPRENT_CONSTVALUE) {
|
||||
// Compare value
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Exprent.h"
|
||||
#include "VarType.h"
|
||||
#include <variant>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class CheckTypesResult;
|
||||
class StructMember;
|
||||
|
||||
// Java's Object value in ConstExprent — int/long/float/double/string or null
|
||||
using ConstValue = std::variant<std::monostate, int32_t, int64_t, float, double, std::string>;
|
||||
|
||||
class ConstExprent : public Exprent {
|
||||
public:
|
||||
// Construct from int with auto type guessing (bool permitted)
|
||||
ConstExprent(int32_t val, bool boolPermitted, uint64_t bytecodeOffsets);
|
||||
// Construct from VarType + variant value
|
||||
ConstExprent(VarType constType, ConstValue value, uint64_t bytecodeOffsets);
|
||||
// With parent (for enum-member contexts)
|
||||
ConstExprent(VarType constType, ConstValue value, uint64_t bytecodeOffsets,
|
||||
StructMember* parent);
|
||||
|
||||
VarType getExprType() const override { return constType_; }
|
||||
int getExprentUse() const override {
|
||||
return Exprent::MULTIPLE_USES | Exprent::SIDE_EFFECTS_FREE;
|
||||
}
|
||||
|
||||
CheckTypesResult* checkExprTypeBounds();
|
||||
std::vector<Exprent*> getAllExprents() const override { return {}; }
|
||||
Exprent* copy() const override;
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
bool equals(const Exprent* o) const;
|
||||
void fillBytecodeRange(uint64_t* values) const override;
|
||||
|
||||
bool match(MatchNode* matchNode, MatchEngine* engine) override;
|
||||
|
||||
VarType getConstType() const { return constType_; }
|
||||
void setConstType(const VarType& t) { constType_ = t; }
|
||||
ConstValue getValue() const { return value_; }
|
||||
|
||||
int32_t getIntValue() const;
|
||||
int64_t getLongValue() const;
|
||||
float getFloatValue() const;
|
||||
double getDoubleValue() const;
|
||||
|
||||
bool isNull() const;
|
||||
bool isBoolPermitted() const { return boolPermitted_; }
|
||||
|
||||
void adjustConstType(const VarType& expectedType);
|
||||
|
||||
static VarType guessType(int32_t val, bool boolPermitted);
|
||||
|
||||
private:
|
||||
VarType constType_;
|
||||
ConstValue value_;
|
||||
bool boolPermitted_ = false;
|
||||
StructMember* parent_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,400 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ConstantPool.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "DataInputFullStream.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ===========================================================================
|
||||
// ConstantPool – constructor: parse and multi-pass resolve
|
||||
// ===========================================================================
|
||||
|
||||
ConstantPool::ConstantPool(DataInputFullStream& in, PoolInterceptor* interceptor)
|
||||
: interceptor_(interceptor)
|
||||
{
|
||||
using CC = CodeConstants;
|
||||
|
||||
// The pool count is one more than the number of usable entries (index 0 is unused).
|
||||
int32_t poolCount = in.readUnsignedShort();
|
||||
pool_.reserve(static_cast<size_t>(poolCount));
|
||||
|
||||
// Three resolution passes (mirrors the Java BitSet approach):
|
||||
// pass0: CONSTANT_Class, String, MethodType, Module, Package + NameAndType
|
||||
// pass1: Fieldref, Methodref, InterfaceMethodref, Dynamic, InvokeDynamic
|
||||
// pass2: MethodHandle
|
||||
std::vector<int32_t> pass0, pass1, pass2;
|
||||
|
||||
// Slot 0 is always null (dummy entry).
|
||||
pool_.push_back(nullptr);
|
||||
|
||||
for (int32_t i = 1; i < poolCount; ++i) {
|
||||
int32_t tag = in.readUnsignedByte();
|
||||
|
||||
switch (tag) {
|
||||
case CC::CONSTANT_Utf8:
|
||||
pool_.push_back(std::make_shared<PrimitiveConstant>(
|
||||
CC::CONSTANT_Utf8, PrimitiveValue{in.readUTF()}));
|
||||
break;
|
||||
|
||||
case CC::CONSTANT_Integer:
|
||||
pool_.push_back(std::make_shared<PrimitiveConstant>(
|
||||
CC::CONSTANT_Integer, PrimitiveValue{in.readInt()}));
|
||||
break;
|
||||
|
||||
case CC::CONSTANT_Float:
|
||||
pool_.push_back(std::make_shared<PrimitiveConstant>(
|
||||
CC::CONSTANT_Float, PrimitiveValue{in.readFloat()}));
|
||||
break;
|
||||
|
||||
case CC::CONSTANT_Long:
|
||||
pool_.push_back(std::make_shared<PrimitiveConstant>(
|
||||
CC::CONSTANT_Long, PrimitiveValue{in.readLong()}));
|
||||
pool_.push_back(nullptr); // long occupies two cp slots
|
||||
++i;
|
||||
break;
|
||||
|
||||
case CC::CONSTANT_Double:
|
||||
pool_.push_back(std::make_shared<PrimitiveConstant>(
|
||||
CC::CONSTANT_Double, PrimitiveValue{in.readDouble()}));
|
||||
pool_.push_back(nullptr); // double occupies two cp slots
|
||||
++i;
|
||||
break;
|
||||
|
||||
// Single-index entries: resolved in pass 0.
|
||||
case CC::CONSTANT_Class:
|
||||
case CC::CONSTANT_String:
|
||||
case CC::CONSTANT_MethodType:
|
||||
case CC::CONSTANT_Module:
|
||||
case CC::CONSTANT_Package: {
|
||||
int32_t idx = in.readUnsignedShort();
|
||||
pool_.push_back(std::make_shared<PrimitiveConstant>(tag, idx));
|
||||
pass0.push_back(i);
|
||||
break;
|
||||
}
|
||||
|
||||
// NameAndType: two indices resolved in pass 0 (before refs).
|
||||
case CC::CONSTANT_NameAndType: {
|
||||
int32_t idx1 = in.readUnsignedShort();
|
||||
int32_t idx2 = in.readUnsignedShort();
|
||||
pool_.push_back(std::make_shared<LinkConstant>(tag, idx1, idx2));
|
||||
pass0.push_back(i);
|
||||
break;
|
||||
}
|
||||
|
||||
// Field/method refs: resolved after NameAndType (pass 1).
|
||||
case CC::CONSTANT_Fieldref:
|
||||
case CC::CONSTANT_Methodref:
|
||||
case CC::CONSTANT_InterfaceMethodref:
|
||||
case CC::CONSTANT_Dynamic:
|
||||
case CC::CONSTANT_InvokeDynamic: {
|
||||
int32_t idx1 = in.readUnsignedShort();
|
||||
int32_t idx2 = in.readUnsignedShort();
|
||||
pool_.push_back(std::make_shared<LinkConstant>(tag, idx1, idx2));
|
||||
pass1.push_back(i);
|
||||
break;
|
||||
}
|
||||
|
||||
// MethodHandle: reference_kind (u1) + reference_index (u2).
|
||||
// Resolved last because its target may be a Methodref (pass 1).
|
||||
case CC::CONSTANT_MethodHandle: {
|
||||
int32_t refKind = in.readUnsignedByte();
|
||||
int32_t refIndex = in.readUnsignedShort();
|
||||
pool_.push_back(std::make_shared<LinkConstant>(tag, refKind, refIndex));
|
||||
pass2.push_back(i);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw ClassFormatException(
|
||||
"Unsupported constant pool entry type " +
|
||||
std::to_string(static_cast<unsigned>(tag)) +
|
||||
" at index #" + std::to_string(i) + "! ");
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-pass resolution: pass0 → pass1 → pass2
|
||||
auto runPass = [&](const std::vector<int32_t>& indices) {
|
||||
for (int32_t idx : indices) {
|
||||
pool_[static_cast<size_t>(idx)]->resolveConstant(*this);
|
||||
}
|
||||
};
|
||||
|
||||
runPass(pass0);
|
||||
runPass(pass1);
|
||||
runPass(pass2);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// skipPool – fast scan without building any entries
|
||||
// ===========================================================================
|
||||
|
||||
void ConstantPool::skipPool(DataInputFullStream& in) {
|
||||
using CC = CodeConstants;
|
||||
|
||||
int32_t poolCount = in.readUnsignedShort();
|
||||
|
||||
for (int32_t i = 1; i < poolCount; ++i) {
|
||||
int32_t tag = in.readUnsignedByte();
|
||||
|
||||
switch (tag) {
|
||||
case CC::CONSTANT_Utf8:
|
||||
in.readUTF(); // reads and discards
|
||||
break;
|
||||
case CC::CONSTANT_Integer:
|
||||
case CC::CONSTANT_Float:
|
||||
case CC::CONSTANT_Fieldref:
|
||||
case CC::CONSTANT_Methodref:
|
||||
case CC::CONSTANT_InterfaceMethodref:
|
||||
case CC::CONSTANT_NameAndType:
|
||||
case CC::CONSTANT_Dynamic:
|
||||
case CC::CONSTANT_InvokeDynamic:
|
||||
in.discard(4);
|
||||
break;
|
||||
case CC::CONSTANT_Long:
|
||||
case CC::CONSTANT_Double:
|
||||
in.discard(8);
|
||||
++i; // occupies two slots
|
||||
break;
|
||||
case CC::CONSTANT_Class:
|
||||
case CC::CONSTANT_String:
|
||||
case CC::CONSTANT_MethodType:
|
||||
case CC::CONSTANT_Module:
|
||||
case CC::CONSTANT_Package:
|
||||
in.discard(2);
|
||||
break;
|
||||
case CC::CONSTANT_MethodHandle:
|
||||
in.discard(3);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"Invalid Constant Pool entry #" + std::to_string(i) +
|
||||
" Type: " + std::to_string(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Accessors
|
||||
// ===========================================================================
|
||||
|
||||
PooledConstant* ConstantPool::getConstant(int32_t index) const {
|
||||
return pool_.at(static_cast<size_t>(index)).get();
|
||||
}
|
||||
|
||||
PrimitiveConstant* ConstantPool::getPrimitiveConstant(int32_t index) const {
|
||||
auto* cn = dynamic_cast<PrimitiveConstant*>(getConstant(index));
|
||||
if (!cn) return nullptr;
|
||||
|
||||
if (interceptor_ && cn->type == CodeConstants::CONSTANT_Class) {
|
||||
std::string newName = buildNewClassname(cn->getString());
|
||||
if (!newName.empty()) {
|
||||
auto renamed = std::make_shared<PrimitiveConstant>(
|
||||
CodeConstants::CONSTANT_Class, PrimitiveValue{newName});
|
||||
PrimitiveConstant* raw = renamed.get();
|
||||
renamedCache_.push_back(std::move(renamed));
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
return cn;
|
||||
}
|
||||
|
||||
LinkConstant* ConstantPool::getLinkConstant(int32_t index) const {
|
||||
auto* ln = dynamic_cast<LinkConstant*>(getConstant(index));
|
||||
if (!ln) return nullptr;
|
||||
|
||||
if (interceptor_ &&
|
||||
(ln->type == CodeConstants::CONSTANT_Fieldref ||
|
||||
ln->type == CodeConstants::CONSTANT_Methodref ||
|
||||
ln->type == CodeConstants::CONSTANT_InterfaceMethodref))
|
||||
{
|
||||
std::string newClassName = buildNewClassname(ln->className);
|
||||
std::string newElement = interceptor_->getName(
|
||||
ln->className + ' ' + ln->elementName + ' ' + ln->descriptor);
|
||||
std::string newDescriptor = buildNewDescriptor(
|
||||
ln->type == CodeConstants::CONSTANT_Fieldref, ln->descriptor);
|
||||
|
||||
if (!newClassName.empty() || !newElement.empty() || !newDescriptor.empty()) {
|
||||
std::string cn = newClassName.empty() ? ln->className : newClassName;
|
||||
std::string dsc = newDescriptor.empty() ? ln->descriptor : newDescriptor;
|
||||
// newElement is "oldClass oldName oldDesc" → extract the second token.
|
||||
std::string en = ln->elementName;
|
||||
if (!newElement.empty()) {
|
||||
auto sp1 = newElement.find(' ');
|
||||
if (sp1 != std::string::npos) {
|
||||
auto sp2 = newElement.find(' ', sp1 + 1);
|
||||
en = newElement.substr(sp1 + 1, sp2 == std::string::npos
|
||||
? std::string::npos
|
||||
: sp2 - sp1 - 1);
|
||||
}
|
||||
}
|
||||
auto renamedLn = std::make_shared<LinkConstant>(ln->type, cn, en, dsc);
|
||||
LinkConstant* rawLn = renamedLn.get();
|
||||
renamedCache_.push_back(std::move(renamedLn));
|
||||
return rawLn;
|
||||
}
|
||||
}
|
||||
return ln;
|
||||
}
|
||||
|
||||
std::vector<std::string> ConstantPool::getClassElement(int32_t elementType,
|
||||
const std::string& className,
|
||||
int32_t nameIndex,
|
||||
int32_t descriptorIndex) const {
|
||||
auto* nameCn = dynamic_cast<PrimitiveConstant*>(getConstant(nameIndex));
|
||||
auto* descCn = dynamic_cast<PrimitiveConstant*>(getConstant(descriptorIndex));
|
||||
|
||||
std::string elementName = nameCn->getString();
|
||||
std::string descriptor = descCn->getString();
|
||||
|
||||
if (interceptor_) {
|
||||
std::string cn = className;
|
||||
std::string oldName = interceptor_->getOldName(cn);
|
||||
if (!oldName.empty()) cn = oldName;
|
||||
|
||||
std::string newElement = interceptor_->getName(
|
||||
cn + ' ' + elementName + ' ' + descriptor);
|
||||
if (!newElement.empty()) {
|
||||
// Format: "className elementName descriptor"
|
||||
auto sp1 = newElement.find(' ');
|
||||
auto sp2 = (sp1 != std::string::npos)
|
||||
? newElement.find(' ', sp1 + 1)
|
||||
: std::string::npos;
|
||||
if (sp1 != std::string::npos) {
|
||||
elementName = newElement.substr(sp1 + 1,
|
||||
sp2 == std::string::npos ? std::string::npos : sp2 - sp1 - 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::string newDescriptor = buildNewDescriptor(elementType == FIELD, descriptor);
|
||||
if (!newDescriptor.empty()) descriptor = newDescriptor;
|
||||
}
|
||||
|
||||
return {elementName, descriptor};
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// buildNewClassname
|
||||
// ===========================================================================
|
||||
std::string ConstantPool::buildNewClassname(const std::string& className) const {
|
||||
if (!interceptor_) return {};
|
||||
|
||||
// Count and strip leading '[' array dimensions.
|
||||
int32_t arrayDim = 0;
|
||||
std::string inner = className;
|
||||
while (!inner.empty() && inner[0] == '[') {
|
||||
++arrayDim;
|
||||
inner = inner.substr(1);
|
||||
}
|
||||
|
||||
// For array element types in Lclassname; form, strip the L...;
|
||||
std::string lookup = inner;
|
||||
bool isLForm = false;
|
||||
if (arrayDim > 0 && inner.size() >= 2 && inner[0] == 'L' && inner.back() == ';') {
|
||||
lookup = inner.substr(1, inner.size() - 2);
|
||||
isLForm = true;
|
||||
}
|
||||
|
||||
std::string newName = interceptor_->getName(lookup);
|
||||
if (newName.empty()) return {};
|
||||
|
||||
if (arrayDim > 0) {
|
||||
std::string result(static_cast<size_t>(arrayDim), '[');
|
||||
result += 'L';
|
||||
result += newName;
|
||||
result += ';';
|
||||
return result;
|
||||
}
|
||||
return newName;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// buildNewDescriptor (private)
|
||||
// ===========================================================================
|
||||
namespace {
|
||||
|
||||
// Rebuilds a single type starting at desc[pos], advancing pos.
|
||||
// Returns a non-empty string only when a rename was applied.
|
||||
std::string rebuildSingleType(const std::string& desc, size_t& pos,
|
||||
const ConstantPool& pool) {
|
||||
if (pos >= desc.size()) return {};
|
||||
char c = desc[pos];
|
||||
|
||||
if (c == 'B' || c == 'C' || c == 'D' || c == 'F' ||
|
||||
c == 'I' || c == 'J' || c == 'S' || c == 'Z' || c == 'V') {
|
||||
++pos;
|
||||
return {}; // primitives never change name
|
||||
}
|
||||
if (c == '[') {
|
||||
++pos;
|
||||
std::string inner = rebuildSingleType(desc, pos, pool);
|
||||
return inner.empty() ? std::string{} : ('[' + inner);
|
||||
}
|
||||
if (c == 'L') {
|
||||
size_t semi = desc.find(';', pos + 1);
|
||||
if (semi == std::string::npos) { ++pos; return {}; }
|
||||
std::string cn = desc.substr(pos + 1, semi - pos - 1);
|
||||
pos = semi + 1;
|
||||
std::string newCn = pool.buildNewClassname(cn);
|
||||
if (!newCn.empty()) return 'L' + newCn + ';';
|
||||
return {};
|
||||
}
|
||||
// Unknown character – skip.
|
||||
++pos;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string rebuildFieldDesc(const std::string& desc, const ConstantPool& pool) {
|
||||
size_t pos = 0;
|
||||
return rebuildSingleType(desc, pos, pool);
|
||||
}
|
||||
|
||||
std::string rebuildMethodDesc(const std::string& desc, const ConstantPool& pool) {
|
||||
if (desc.empty() || desc[0] != '(') return {};
|
||||
|
||||
bool changed = false;
|
||||
std::string result;
|
||||
result.reserve(desc.size());
|
||||
result += '(';
|
||||
|
||||
size_t pos = 1;
|
||||
while (pos < desc.size() && desc[pos] != ')') {
|
||||
size_t startPos = pos;
|
||||
std::string newType = rebuildSingleType(desc, pos, pool);
|
||||
if (!newType.empty()) {
|
||||
changed = true;
|
||||
result += newType;
|
||||
} else {
|
||||
result += desc.substr(startPos, pos - startPos);
|
||||
}
|
||||
}
|
||||
if (pos < desc.size()) {
|
||||
result += ')';
|
||||
++pos;
|
||||
size_t startPos = pos;
|
||||
std::string newRet = rebuildSingleType(desc, pos, pool);
|
||||
if (!newRet.empty()) {
|
||||
changed = true;
|
||||
result += newRet;
|
||||
} else {
|
||||
result += desc.substr(startPos, pos - startPos);
|
||||
}
|
||||
}
|
||||
|
||||
return changed ? result : std::string{};
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
std::string ConstantPool::buildNewDescriptor(bool isField,
|
||||
const std::string& descriptor) const {
|
||||
if (!interceptor_) return {};
|
||||
return isField ? rebuildFieldDesc(descriptor, *this)
|
||||
: rebuildMethodDesc(descriptor, *this);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "PooledConstant.h"
|
||||
#include "PrimitiveConstant.h"
|
||||
#include "LinkConstant.h"
|
||||
#include "ClassFormatException.h"
|
||||
#include "DataInputFullStream.h"
|
||||
#include "PoolInterceptor.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ConstantPool
|
||||
// ---------------------------------------------------------------------------
|
||||
class ConstantPool {
|
||||
public:
|
||||
static constexpr int32_t FIELD = 1;
|
||||
static constexpr int32_t METHOD = 2;
|
||||
|
||||
// Parse the constant pool from a stream positioned just after the magic/version
|
||||
// fields of a .class file (i.e. the stream cursor is at the cp_count u2).
|
||||
// Throws ClassFormatException on malformed data.
|
||||
// interceptor may be nullptr.
|
||||
explicit ConstantPool(DataInputFullStream& in,
|
||||
PoolInterceptor* interceptor = nullptr);
|
||||
|
||||
// Skip over a constant pool without building it (fast path for StructContext scanning).
|
||||
static void skipPool(DataInputFullStream& in);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Accessors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Returns the raw constant at the given index (may be nullptr for long/double slots).
|
||||
PooledConstant* getConstant(int32_t index) const;
|
||||
|
||||
// Returns the PrimitiveConstant at index, applying interceptor renaming for
|
||||
// CONSTANT_Class entries. Returns nullptr for null slots.
|
||||
PrimitiveConstant* getPrimitiveConstant(int32_t index) const;
|
||||
|
||||
// Returns the LinkConstant at index, applying interceptor renaming for
|
||||
// Fieldref/Methodref/InterfaceMethodref entries. Returns nullptr for null slots.
|
||||
LinkConstant* getLinkConstant(int32_t index) const;
|
||||
|
||||
// Returns {elementName, descriptor} for a field or method belonging to className.
|
||||
// Applies interceptor renaming when available.
|
||||
std::vector<std::string> getClassElement(int32_t elementType,
|
||||
const std::string& className,
|
||||
int32_t nameIndex,
|
||||
int32_t descriptorIndex) const;
|
||||
|
||||
// Implements NewClassNameBuilder: maps className through the interceptor.
|
||||
// Returns empty string if no renaming applies.
|
||||
std::string buildNewClassname(const std::string& className) const;
|
||||
|
||||
int32_t size() const { return static_cast<int32_t>(pool_.size()); }
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<PooledConstant>> pool_;
|
||||
|
||||
// Non-owning; the interceptor lifetime is managed by the caller.
|
||||
PoolInterceptor* interceptor_{nullptr};
|
||||
|
||||
// Mutable cache for synthetic constants created by interceptor renaming
|
||||
// (both PrimitiveConstant and LinkConstant instances are stored here).
|
||||
mutable std::vector<std::shared_ptr<PooledConstant>> renamedCache_;
|
||||
|
||||
// Build a new descriptor string through the interceptor for class references
|
||||
// embedded in field or method descriptors.
|
||||
std::string buildNewDescriptor(bool isField, const std::string& descriptor) const;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ContextUnit.h"
|
||||
#include "IDecompiledData.h"
|
||||
#include "LazyLoader.h"
|
||||
#include "StructClass.h"
|
||||
#include "DataInputFullStream.h"
|
||||
#include "DecompilerContext.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
ContextUnit::ContextUnit(int type, const std::string* path, const std::string& archiveName,
|
||||
bool isOwn, IResultSaver* saver, IDecompiledData* decompiledData)
|
||||
: type_(type)
|
||||
, path_(path ? *path : "")
|
||||
, archiveName_(archiveName)
|
||||
, isOwn_(isOwn)
|
||||
, saver_(saver)
|
||||
, decompiledData_(decompiledData) {
|
||||
}
|
||||
|
||||
void ContextUnit::addClass(StructClass* cl, const std::string& entryName) {
|
||||
classes_.push_back(cl);
|
||||
classEntryNames_.push_back(entryName);
|
||||
}
|
||||
|
||||
void ContextUnit::addOtherEntry(const std::string& externalPath, const std::string& entryName) {
|
||||
otherEntries_.push_back({externalPath, entryName});
|
||||
}
|
||||
|
||||
void ContextUnit::addDirEntry(const std::string& entryName) {
|
||||
dirEntries_.push_back(entryName);
|
||||
}
|
||||
|
||||
void ContextUnit::setManifest(const Manifest& manifest) {
|
||||
manifest_ = manifest;
|
||||
}
|
||||
|
||||
void ContextUnit::reload(LazyLoader* loader) {
|
||||
std::vector<StructClass*> oldClasses = classes_;
|
||||
classes_.clear();
|
||||
|
||||
for (size_t i = 0; i < oldClasses.size(); i++) {
|
||||
StructClass* oldCl = oldClasses[i];
|
||||
const std::string& entryName = classEntryNames_[i];
|
||||
|
||||
try {
|
||||
auto stream = loader->getClassStream(oldCl->qualifiedName);
|
||||
if (stream) {
|
||||
auto newCl = StructClass::create(*stream, oldCl->isOwn(), loader);
|
||||
classes_.push_back(newCl.release());
|
||||
}
|
||||
} catch (...) {
|
||||
DecompilerContext::getLogger()->writeMessage("Failed to reload class: " + oldCl->qualifiedName,
|
||||
IFernflowerLogger::Severity::WARN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContextUnit::save() {
|
||||
if (type_ == TYPE_FOLDER) {
|
||||
saver_->saveFolder(path_);
|
||||
|
||||
for (const auto& entry : dirEntries_) {
|
||||
saver_->saveDirEntry(path_, archiveName_, entry);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < classes_.size(); i++) {
|
||||
StructClass* cl = classes_[i];
|
||||
const std::string& entryName = classEntryNames_[i];
|
||||
|
||||
std::string classEntryName = decompiledData_->getClassEntryName(*cl, entryName);
|
||||
if (!classEntryName.empty()) {
|
||||
std::string content = decompiledData_->getClassContent(*cl);
|
||||
saver_->saveClassFile(path_, cl->qualifiedName, classEntryName, content, {});
|
||||
} else {
|
||||
saver_->copyFile(entryName, path_, entryName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entry : otherEntries_) {
|
||||
saver_->copyFile(entry.first, path_, entry.second);
|
||||
}
|
||||
} else {
|
||||
// JAR/ZIP
|
||||
saver_->createArchive(path_, archiveName_, manifest_);
|
||||
|
||||
for (const auto& dirEntry : dirEntries_) {
|
||||
saver_->saveDirEntry(path_, archiveName_, dirEntry);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < classes_.size(); i++) {
|
||||
StructClass* cl = classes_[i];
|
||||
const std::string& entryName = classEntryNames_[i];
|
||||
|
||||
std::string classEntryName = decompiledData_->getClassEntryName(*cl, entryName);
|
||||
if (!classEntryName.empty()) {
|
||||
std::string content = decompiledData_->getClassContent(*cl);
|
||||
saver_->saveClassEntry(path_, archiveName_, cl->qualifiedName, classEntryName, content);
|
||||
} else {
|
||||
saver_->copyEntry(entryName, path_, archiveName_, entryName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entry : otherEntries_) {
|
||||
saver_->copyEntry(entry.first, path_, archiveName_, entry.second);
|
||||
}
|
||||
|
||||
saver_->closeArchive(path_, archiveName_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "IResultSaver.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class IDecompiledData;
|
||||
class LazyLoader;
|
||||
class StructClass;
|
||||
|
||||
class ContextUnit {
|
||||
public:
|
||||
static constexpr int TYPE_FOLDER = 0;
|
||||
static constexpr int TYPE_JAR = 1;
|
||||
static constexpr int TYPE_ZIP = 2;
|
||||
|
||||
ContextUnit(int type, const std::string* path, const std::string& archiveName,
|
||||
bool isOwn, IResultSaver* saver, IDecompiledData* decompiledData);
|
||||
|
||||
void addClass(StructClass* cl, const std::string& entryName);
|
||||
void addOtherEntry(const std::string& externalPath, const std::string& entryName);
|
||||
void addDirEntry(const std::string& entryName);
|
||||
void setManifest(const Manifest& manifest);
|
||||
|
||||
void reload(LazyLoader* loader);
|
||||
void save();
|
||||
|
||||
bool isOwn() const { return isOwn_; }
|
||||
const std::vector<StructClass*>& getClasses() const { return classes_; }
|
||||
|
||||
private:
|
||||
int type_;
|
||||
std::string path_;
|
||||
std::string archiveName_;
|
||||
bool isOwn_;
|
||||
IResultSaver* saver_;
|
||||
IDecompiledData* decompiledData_;
|
||||
Manifest manifest_;
|
||||
|
||||
std::vector<StructClass*> classes_;
|
||||
std::vector<std::pair<std::string,std::string>> otherEntries_;
|
||||
std::vector<std::string> dirEntries_;
|
||||
std::vector<std::string> classEntryNames_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,609 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ControlFlowGraph.h"
|
||||
#include "BasicBlock.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "ConstantPool.h"
|
||||
#include "DataPoint.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "ExceptionHandler.h"
|
||||
#include "ExceptionRangeCFG.h"
|
||||
#include "Instruction.h"
|
||||
#include "InstructionSequence.h"
|
||||
#include "JumpInstruction.h"
|
||||
#include "SwitchInstruction.h"
|
||||
#include "StructClass.h"
|
||||
#include "StructMethod.h"
|
||||
#include "VarType.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
namespace ff {
|
||||
|
||||
ControlFlowGraph::ControlFlowGraph(InstructionSequence* seq) {
|
||||
buildBlocks(seq);
|
||||
}
|
||||
|
||||
ControlFlowGraph::~ControlFlowGraph() {
|
||||
// Clean up owned blocks
|
||||
for (int i = 0; i < blocks_.size(); i++) {
|
||||
delete blocks_.get(i);
|
||||
}
|
||||
delete last_;
|
||||
for (auto* r : exceptions_) delete r;
|
||||
}
|
||||
|
||||
void ControlFlowGraph::removeMarkers() {
|
||||
for (int i = 0; i < blocks_.size(); i++) {
|
||||
blocks_.get(i)->mark = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ControlFlowGraph::toString() const {
|
||||
if (blocks_.isEmpty()) return "Empty";
|
||||
|
||||
std::string sep = DecompilerContext::getNewLineSeparator();
|
||||
std::ostringstream buf;
|
||||
|
||||
for (int bi = 0; bi < blocks_.size(); bi++) {
|
||||
BasicBlock* block = blocks_.get(bi);
|
||||
buf << "----- Block " << block->id << " -----" << sep;
|
||||
buf << block->toString();
|
||||
buf << "----- Edges -----" << sep;
|
||||
|
||||
for (BasicBlock* suc : block->getSuccessors()) {
|
||||
buf << ">>>>>>>>(regular) Block " << suc->id << sep;
|
||||
}
|
||||
for (BasicBlock* handler : block->getSuccessorExceptions()) {
|
||||
ExceptionRangeCFG* range = getExceptionRange(handler, block);
|
||||
if (!range) {
|
||||
buf << ">>>>>>>>(exception) Block " << handler->id << "\tERROR: range not found!" << sep;
|
||||
} else {
|
||||
auto* types = range->getExceptionTypes();
|
||||
if (!types) {
|
||||
buf << ">>>>>>>>(exception) Block " << handler->id << "\tNULL" << sep;
|
||||
} else {
|
||||
for (const auto& t : *types) {
|
||||
buf << ">>>>>>>>(exception) Block " << handler->id << "\t" << t << sep;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buf << "----- ----- -----" << sep;
|
||||
}
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
void ControlFlowGraph::removeBlock(BasicBlock* block) {
|
||||
while (!block->getSuccessors().empty()) {
|
||||
block->removeSuccessor(block->getSuccessors()[0]);
|
||||
}
|
||||
while (!block->getSuccessorExceptions().empty()) {
|
||||
block->removeSuccessorException(block->getSuccessorExceptions()[0]);
|
||||
}
|
||||
while (!block->getPredecessors().empty()) {
|
||||
block->getPredecessors()[0]->removeSuccessor(block);
|
||||
}
|
||||
while (!block->getPredecessorExceptions().empty()) {
|
||||
block->getPredecessorExceptions()[0]->removeSuccessorException(block);
|
||||
}
|
||||
|
||||
last_->removePredecessor(block);
|
||||
blocks_.removeWithKey(block->id);
|
||||
|
||||
for (int i = (int)exceptions_.size() - 1; i >= 0; i--) {
|
||||
ExceptionRangeCFG* range = exceptions_[i];
|
||||
if (range->getHandler() == block) {
|
||||
exceptions_.erase(exceptions_.begin() + i);
|
||||
delete range;
|
||||
} else {
|
||||
auto& lstRange = range->getProtectedRange();
|
||||
lstRange.erase(std::remove(lstRange.begin(), lstRange.end(), block), lstRange.end());
|
||||
if (lstRange.empty()) {
|
||||
exceptions_.erase(exceptions_.begin() + i);
|
||||
delete range;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subroutines_.erase(block);
|
||||
for (auto it = subroutines_.begin(); it != subroutines_.end(); ) {
|
||||
if (it->second == block) it = subroutines_.erase(it);
|
||||
else ++it;
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionRangeCFG* ControlFlowGraph::getExceptionRange(BasicBlock* handler, BasicBlock* block) const {
|
||||
for (int i = (int)exceptions_.size() - 1; i >= 0; i--) {
|
||||
ExceptionRangeCFG* range = exceptions_[i];
|
||||
auto& pr = range->getProtectedRange();
|
||||
if (range->getHandler() == handler &&
|
||||
std::find(pr.begin(), pr.end(), block) != pr.end()) {
|
||||
return range;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// buildBlocks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void ControlFlowGraph::buildBlocks(InstructionSequence* instrseq) {
|
||||
auto states = findStartInstructions(instrseq);
|
||||
std::unordered_map<int, BasicBlock*> mapInstrBlocks;
|
||||
blocks_ = createBasicBlocks(states, instrseq, mapInstrBlocks);
|
||||
first_ = blocks_.get(0);
|
||||
|
||||
last_ = new BasicBlock(++last_id);
|
||||
|
||||
connectBlocks(blocks_, mapInstrBlocks);
|
||||
setExceptionEdges(instrseq, mapInstrBlocks);
|
||||
setSubroutineEdges();
|
||||
setFirstAndLastBlocks();
|
||||
}
|
||||
|
||||
std::vector<short> ControlFlowGraph::findStartInstructions(InstructionSequence* seq) {
|
||||
int len = seq->length();
|
||||
std::vector<short> inststates(len, 0);
|
||||
|
||||
std::unordered_set<int> excSet;
|
||||
for (auto& handler : seq->getExceptionTable().getHandlers()) {
|
||||
excSet.insert(handler.from_instr);
|
||||
excSet.insert(handler.to_instr);
|
||||
excSet.insert(handler.handler_instr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (excSet.count(i)) inststates[i] = 1;
|
||||
|
||||
Instruction* instr = seq->getInstr(i);
|
||||
switch (instr->group) {
|
||||
case CodeConstants::GROUP_JUMP:
|
||||
inststates[static_cast<JumpInstruction*>(instr)->destination] = 1;
|
||||
[[fallthrough]];
|
||||
case CodeConstants::GROUP_RETURN:
|
||||
if (i + 1 < len) inststates[i + 1] = 1;
|
||||
break;
|
||||
case CodeConstants::GROUP_SWITCH: {
|
||||
SwitchInstruction* sw = static_cast<SwitchInstruction*>(instr);
|
||||
for (int d : sw->getDestinations()) inststates[d] = 1;
|
||||
inststates[sw->getDefaultDestination()] = 1;
|
||||
if (i + 1 < len) inststates[i + 1] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inststates[0] = 1;
|
||||
return inststates;
|
||||
}
|
||||
|
||||
VBStyleCollection<BasicBlock*, int> ControlFlowGraph::createBasicBlocks(
|
||||
std::vector<short>& startblock, InstructionSequence* instrseq,
|
||||
std::unordered_map<int, BasicBlock*>& mapInstrBlocks) {
|
||||
|
||||
VBStyleCollection<BasicBlock*, int> col;
|
||||
|
||||
InstructionSequence* currseq = nullptr;
|
||||
std::vector<int>* lstOffs = nullptr;
|
||||
|
||||
int len = (int)startblock.size();
|
||||
short counter = 0;
|
||||
int blockoffset = 0;
|
||||
|
||||
BasicBlock* currentBlock = nullptr;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (startblock[i] == 1) {
|
||||
currentBlock = new BasicBlock(++counter);
|
||||
currseq = currentBlock->getSeq();
|
||||
lstOffs = ¤tBlock->getOriginalOffsets();
|
||||
col.addWithKey(currentBlock, currentBlock->id);
|
||||
blockoffset = instrseq->getOffset(i);
|
||||
}
|
||||
|
||||
startblock[i] = counter;
|
||||
mapInstrBlocks[i] = currentBlock;
|
||||
|
||||
currseq->addInstruction(instrseq->getInstr(i)->clone(), instrseq->getOffset(i) - blockoffset);
|
||||
lstOffs->push_back(instrseq->getOffset(i));
|
||||
}
|
||||
|
||||
last_id = counter;
|
||||
return col;
|
||||
}
|
||||
|
||||
void ControlFlowGraph::connectBlocks(VBStyleCollection<BasicBlock*, int>& lstbb,
|
||||
std::unordered_map<int, BasicBlock*>& mapInstrBlocks) {
|
||||
for (int i = 0; i < lstbb.size(); i++) {
|
||||
BasicBlock* block = lstbb.get(i);
|
||||
Instruction* instr = block->getLastInstruction();
|
||||
bool fallthrough = instr->canFallThrough();
|
||||
|
||||
switch (instr->group) {
|
||||
case CodeConstants::GROUP_JUMP: {
|
||||
int dest = static_cast<JumpInstruction*>(instr)->destination;
|
||||
block->addSuccessor(mapInstrBlocks.at(dest));
|
||||
break;
|
||||
}
|
||||
case CodeConstants::GROUP_SWITCH: {
|
||||
SwitchInstruction* sinstr = static_cast<SwitchInstruction*>(instr);
|
||||
block->addSuccessor(mapInstrBlocks.at(sinstr->getDefaultDestination()));
|
||||
for (int d : sinstr->getDestinations()) {
|
||||
block->addSuccessor(mapInstrBlocks.at(d));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fallthrough && i < lstbb.size() - 1) {
|
||||
block->addSuccessor(lstbb.get(i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowGraph::setExceptionEdges(InstructionSequence* instrseq,
|
||||
std::unordered_map<int, BasicBlock*>& instrBlocks) {
|
||||
std::unordered_map<std::string, ExceptionRangeCFG*> mapRanges;
|
||||
|
||||
for (auto& handler : instrseq->getExceptionTable().getHandlers()) {
|
||||
BasicBlock* from = instrBlocks.at(handler.from_instr);
|
||||
BasicBlock* to = instrBlocks.at(handler.to_instr);
|
||||
BasicBlock* handle = instrBlocks.at(handler.handler_instr);
|
||||
|
||||
std::string key = std::to_string(from->id) + ":" + std::to_string(to->id) + ":" + std::to_string(handle->id);
|
||||
|
||||
auto it = mapRanges.find(key);
|
||||
if (it != mapRanges.end()) {
|
||||
it->second->addExceptionType(handler.exceptionClass);
|
||||
} else {
|
||||
std::vector<BasicBlock*> protectedRange;
|
||||
for (int j = from->id; j < to->id; j++) {
|
||||
auto* blockPtr = blocks_.getWithKey(j);
|
||||
if (blockPtr && *blockPtr) {
|
||||
BasicBlock* block = *blockPtr;
|
||||
protectedRange.push_back(block);
|
||||
block->addSuccessorException(handle);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string>* excTypes = nullptr;
|
||||
std::vector<std::string> excVec;
|
||||
if (!handler.exceptionClass.empty()) {
|
||||
excVec.push_back(handler.exceptionClass);
|
||||
excTypes = &excVec;
|
||||
}
|
||||
|
||||
ExceptionRangeCFG* range = new ExceptionRangeCFG(protectedRange, handle, excTypes);
|
||||
mapRanges[key] = range;
|
||||
exceptions_.push_back(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowGraph::setSubroutineEdges() {
|
||||
for (int bi = 0; bi < blocks_.size(); bi++) {
|
||||
BasicBlock* block = blocks_.get(bi);
|
||||
|
||||
if (block->getSeq()->getLastInstr() &&
|
||||
block->getSeq()->getLastInstr()->opcode == CodeConstants::opc_jsr) {
|
||||
|
||||
std::vector<BasicBlock*> stack;
|
||||
std::vector<std::vector<BasicBlock*>> stackJsrStacks;
|
||||
std::unordered_set<BasicBlock*> setVisited;
|
||||
|
||||
stack.push_back(block);
|
||||
stackJsrStacks.push_back({});
|
||||
|
||||
while (!stack.empty()) {
|
||||
BasicBlock* node = stack.front(); stack.erase(stack.begin());
|
||||
std::vector<BasicBlock*> jsrstack = stackJsrStacks.front(); stackJsrStacks.erase(stackJsrStacks.begin());
|
||||
|
||||
setVisited.insert(node);
|
||||
|
||||
int lastop = node->getSeq()->getLastInstr() ? node->getSeq()->getLastInstr()->opcode : -1;
|
||||
if (lastop == CodeConstants::opc_jsr) {
|
||||
jsrstack.push_back(node);
|
||||
} else if (lastop == CodeConstants::opc_ret) {
|
||||
if (!jsrstack.empty()) {
|
||||
BasicBlock* enter = jsrstack.back();
|
||||
auto* exitPtr = blocks_.getWithKey(enter->id + 1);
|
||||
BasicBlock* exit = exitPtr ? *exitPtr : nullptr;
|
||||
if (exit) {
|
||||
if (!node->isSuccessor(exit)) {
|
||||
node->addSuccessor(exit);
|
||||
}
|
||||
jsrstack.pop_back();
|
||||
subroutines_[enter] = exit;
|
||||
} else {
|
||||
throw std::runtime_error("ERROR: last instruction jsr");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!jsrstack.empty()) {
|
||||
for (BasicBlock* succ : node->getSuccessors()) {
|
||||
if (!setVisited.count(succ)) {
|
||||
stack.push_back(succ);
|
||||
stackJsrStacks.push_back(jsrstack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowGraph::setFirstAndLastBlocks() {
|
||||
for (int i = 0; i < blocks_.size(); i++) {
|
||||
BasicBlock* block = blocks_.get(i);
|
||||
if (block->getSuccessors().empty()) {
|
||||
last_->addPredecessor(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JSR inlining
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void ControlFlowGraph::inlineJsr(StructClass* cl, StructMethod* mt) {
|
||||
processJsr();
|
||||
removeJsr(cl, mt);
|
||||
removeMarkers();
|
||||
// DeadCodeHelper::removeEmptyBlocks(this); // called externally
|
||||
}
|
||||
|
||||
void ControlFlowGraph::processJsr() {
|
||||
while (processJsrRanges() != 0) {}
|
||||
}
|
||||
|
||||
int ControlFlowGraph::processJsrRanges() {
|
||||
std::vector<JsrRecord> lstJsrAll;
|
||||
|
||||
for (auto& [jsr, ret] : subroutines_) {
|
||||
lstJsrAll.push_back({jsr, getJsrRange(jsr, ret), ret});
|
||||
}
|
||||
|
||||
// sort so inner ranges come first
|
||||
std::vector<JsrRecord> lstJsr;
|
||||
for (auto& arr : lstJsrAll) {
|
||||
int i = 0;
|
||||
for (; i < (int)lstJsr.size(); i++) {
|
||||
if (lstJsr[i].range.count(arr.jsr)) break;
|
||||
}
|
||||
lstJsr.insert(lstJsr.begin() + i, arr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)lstJsr.size(); i++) {
|
||||
auto& set = lstJsr[i].range;
|
||||
for (int j = i + 1; j < (int)lstJsr.size(); j++) {
|
||||
auto& set1 = lstJsr[j].range;
|
||||
if (!set.count(lstJsr[j].jsr) && !set1.count(lstJsr[i].jsr)) {
|
||||
std::unordered_set<BasicBlock*> setc;
|
||||
for (auto* b : set) {
|
||||
if (set1.count(b)) setc.insert(b);
|
||||
}
|
||||
if (!setc.empty()) {
|
||||
splitJsrRange(lstJsr[i].jsr, lstJsr[i].ret, setc);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unordered_set<BasicBlock*> ControlFlowGraph::getJsrRange(BasicBlock* jsr, BasicBlock* ret) {
|
||||
std::unordered_set<BasicBlock*> blocks;
|
||||
std::vector<BasicBlock*> lstNodes;
|
||||
lstNodes.push_back(jsr);
|
||||
BasicBlock* dom = jsr->getSuccessors()[0];
|
||||
|
||||
while (!lstNodes.empty()) {
|
||||
BasicBlock* node = lstNodes.front(); lstNodes.erase(lstNodes.begin());
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
std::vector<BasicBlock*>* lst;
|
||||
if (j == 0) {
|
||||
if (node->getLastInstruction() &&
|
||||
node->getLastInstruction()->opcode == CodeConstants::opc_ret) {
|
||||
if (std::find(node->getSuccessors().begin(), node->getSuccessors().end(), ret) != node->getSuccessors().end())
|
||||
continue;
|
||||
}
|
||||
lst = &node->getSuccessors();
|
||||
} else {
|
||||
if (node == jsr) continue;
|
||||
lst = &node->getSuccessorExceptions();
|
||||
}
|
||||
|
||||
for (int i = (int)lst->size() - 1; i >= 0; i--) {
|
||||
BasicBlock* child = (*lst)[i];
|
||||
if (blocks.count(child)) continue;
|
||||
|
||||
bool skip = false;
|
||||
if (node != jsr) {
|
||||
for (auto* pred : child->getPredecessors()) {
|
||||
// simplified dominator check - not implementing isDominator here
|
||||
(void)pred; (void)dom;
|
||||
}
|
||||
}
|
||||
if (skip) continue;
|
||||
|
||||
if (child != last_) {
|
||||
blocks.insert(child);
|
||||
}
|
||||
lstNodes.push_back(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
void ControlFlowGraph::splitJsrRange(BasicBlock* jsr, BasicBlock* ret,
|
||||
const std::unordered_set<BasicBlock*>& common_blocks) {
|
||||
std::vector<BasicBlock*> lstNodes;
|
||||
std::unordered_map<int, BasicBlock*> mapNewNodes;
|
||||
|
||||
lstNodes.push_back(jsr);
|
||||
mapNewNodes[jsr->id] = jsr;
|
||||
|
||||
while (!lstNodes.empty()) {
|
||||
BasicBlock* node = lstNodes.front(); lstNodes.erase(lstNodes.begin());
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
std::vector<BasicBlock*>* lst;
|
||||
if (j == 0) {
|
||||
if (node->getLastInstruction() &&
|
||||
node->getLastInstruction()->opcode == CodeConstants::opc_ret) {
|
||||
if (std::find(node->getSuccessors().begin(), node->getSuccessors().end(), ret) != node->getSuccessors().end())
|
||||
continue;
|
||||
}
|
||||
lst = &node->getSuccessors();
|
||||
} else {
|
||||
if (node == jsr) continue;
|
||||
lst = &node->getSuccessorExceptions();
|
||||
}
|
||||
|
||||
for (int i = (int)lst->size() - 1; i >= 0; i--) {
|
||||
BasicBlock* child = (*lst)[i];
|
||||
int childid = child->id;
|
||||
|
||||
if (mapNewNodes.count(childid)) {
|
||||
node->replaceSuccessor(child, mapNewNodes[childid]);
|
||||
} else if (common_blocks.count(child)) {
|
||||
auto clonePtr = child->clone(++last_id);
|
||||
BasicBlock* copy = clonePtr.release();
|
||||
blocks_.addWithKey(copy, copy->id);
|
||||
|
||||
if (copy->getLastInstruction() &&
|
||||
copy->getLastInstruction()->opcode == CodeConstants::opc_ret &&
|
||||
std::find(child->getSuccessors().begin(), child->getSuccessors().end(), ret) != child->getSuccessors().end()) {
|
||||
copy->addSuccessor(ret);
|
||||
child->removeSuccessor(ret);
|
||||
} else {
|
||||
for (BasicBlock* s : child->getSuccessors()) copy->addSuccessor(s);
|
||||
}
|
||||
for (BasicBlock* s : child->getSuccessorExceptions()) copy->addSuccessorException(s);
|
||||
|
||||
lstNodes.push_back(copy);
|
||||
mapNewNodes[childid] = copy;
|
||||
|
||||
auto& lastPreds = last_->getPredecessors();
|
||||
if (std::find(lastPreds.begin(), lastPreds.end(), child) != lastPreds.end()) {
|
||||
last_->addPredecessor(copy);
|
||||
}
|
||||
|
||||
node->replaceSuccessor(child, copy);
|
||||
} else {
|
||||
mapNewNodes[childid] = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
splitJsrExceptionRanges(common_blocks, mapNewNodes);
|
||||
}
|
||||
|
||||
void ControlFlowGraph::splitJsrExceptionRanges(const std::unordered_set<BasicBlock*>& common_blocks,
|
||||
const std::unordered_map<int, BasicBlock*>& mapNewNodes) {
|
||||
for (int i = (int)exceptions_.size() - 1; i >= 0; i--) {
|
||||
ExceptionRangeCFG* range = exceptions_[i];
|
||||
auto& lstRange = range->getProtectedRange();
|
||||
|
||||
std::vector<BasicBlock*> setBoth;
|
||||
for (auto* b : common_blocks) {
|
||||
if (std::find(lstRange.begin(), lstRange.end(), b) != lstRange.end()) {
|
||||
setBoth.push_back(b);
|
||||
}
|
||||
}
|
||||
|
||||
if (!setBoth.empty()) {
|
||||
std::vector<BasicBlock*>* lstNewRange;
|
||||
|
||||
if (setBoth.size() == lstRange.size()) {
|
||||
auto newRangeVec = std::vector<BasicBlock*>();
|
||||
auto it = mapNewNodes.find(range->getHandler()->id);
|
||||
BasicBlock* newHandler = (it != mapNewNodes.end()) ? it->second : range->getHandler();
|
||||
ExceptionRangeCFG* newRange = new ExceptionRangeCFG(newRangeVec, newHandler,
|
||||
range->getExceptionTypes());
|
||||
exceptions_.push_back(newRange);
|
||||
lstNewRange = &newRange->getProtectedRange();
|
||||
} else {
|
||||
lstNewRange = &lstRange;
|
||||
}
|
||||
|
||||
for (BasicBlock* block : setBoth) {
|
||||
auto it = mapNewNodes.find(block->id);
|
||||
lstNewRange->push_back(it != mapNewNodes.end() ? it->second : block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowGraph::removeJsr(StructClass* cl, StructMethod* mt) {
|
||||
// Full implementation calls removeJsrInstructions starting from the first block
|
||||
// with an initial DataPoint computed from the method descriptor. It uses
|
||||
// InstructionImpact to track stack state and rewrites jsr/ret instruction
|
||||
// sequences (used by pre-Java-6 finally blocks) as plain control-flow.
|
||||
(void)cl; (void)mt;
|
||||
}
|
||||
|
||||
void ControlFlowGraph::removeJsrInstructions(ConstantPool* pool, BasicBlock* block, DataPoint data) {
|
||||
// Recursively traverses successor blocks rewriting jsr/ret pairs.
|
||||
// Requires InstructionImpact::getImpact for full stack simulation.
|
||||
(void)pool; (void)block; (void)data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// traversal
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::vector<BasicBlock*> ControlFlowGraph::getReversePostOrder() const {
|
||||
std::vector<BasicBlock*> res;
|
||||
addToReversePostOrderListIterative(first_, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void ControlFlowGraph::addToReversePostOrderListIterative(BasicBlock* root,
|
||||
std::vector<BasicBlock*>& lst) {
|
||||
std::vector<BasicBlock*> stackNode;
|
||||
std::vector<int> stackIndex;
|
||||
std::unordered_set<BasicBlock*> setVisited;
|
||||
|
||||
stackNode.push_back(root);
|
||||
stackIndex.push_back(0);
|
||||
|
||||
while (!stackNode.empty()) {
|
||||
BasicBlock* node = stackNode.back();
|
||||
int index = stackIndex.back();
|
||||
stackIndex.pop_back();
|
||||
|
||||
setVisited.insert(node);
|
||||
|
||||
std::vector<BasicBlock*> lstSuccs = node->getSuccessors();
|
||||
lstSuccs.insert(lstSuccs.end(), node->getSuccessorExceptions().begin(),
|
||||
node->getSuccessorExceptions().end());
|
||||
|
||||
bool pushed = false;
|
||||
for (; index < (int)lstSuccs.size(); index++) {
|
||||
BasicBlock* succ = lstSuccs[index];
|
||||
if (!setVisited.count(succ)) {
|
||||
stackIndex.push_back(index + 1);
|
||||
stackNode.push_back(succ);
|
||||
stackIndex.push_back(0);
|
||||
pushed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushed) {
|
||||
lst.insert(lst.begin(), node);
|
||||
stackNode.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "VBStyleCollection.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class BasicBlock;
|
||||
class ExceptionRangeCFG;
|
||||
class InstructionSequence;
|
||||
class StructClass;
|
||||
class StructMethod;
|
||||
|
||||
class ControlFlowGraph {
|
||||
public:
|
||||
int last_id = 0;
|
||||
|
||||
explicit ControlFlowGraph(InstructionSequence* seq);
|
||||
~ControlFlowGraph();
|
||||
|
||||
void removeMarkers();
|
||||
std::string toString() const;
|
||||
|
||||
void inlineJsr(StructClass* cl, StructMethod* mt);
|
||||
void removeBlock(BasicBlock* block);
|
||||
|
||||
ExceptionRangeCFG* getExceptionRange(BasicBlock* handler, BasicBlock* block) const;
|
||||
|
||||
std::vector<BasicBlock*> getReversePostOrder() const;
|
||||
|
||||
// getters
|
||||
VBStyleCollection<BasicBlock*, int>& getBlocks() { return blocks_; }
|
||||
const VBStyleCollection<BasicBlock*, int>& getBlocks() const { return blocks_; }
|
||||
BasicBlock* getFirst() const { return first_; }
|
||||
void setFirst(BasicBlock* b) { first_ = b; }
|
||||
BasicBlock* getLast() const { return last_; }
|
||||
std::vector<ExceptionRangeCFG*>& getExceptions() { return exceptions_; }
|
||||
const std::vector<ExceptionRangeCFG*>& getExceptions() const { return exceptions_; }
|
||||
std::unordered_set<BasicBlock*>& getFinallyExits() { return finallyExits_; }
|
||||
const std::unordered_set<BasicBlock*>& getFinallyExits() const { return finallyExits_; }
|
||||
|
||||
private:
|
||||
struct JsrRecord {
|
||||
BasicBlock* jsr;
|
||||
std::unordered_set<BasicBlock*> range;
|
||||
BasicBlock* ret;
|
||||
};
|
||||
|
||||
void buildBlocks(InstructionSequence* instrseq);
|
||||
static std::vector<short> findStartInstructions(InstructionSequence* seq);
|
||||
VBStyleCollection<BasicBlock*, int> createBasicBlocks(
|
||||
std::vector<short>& startblock, InstructionSequence* instrseq,
|
||||
std::unordered_map<int, BasicBlock*>& mapInstrBlocks);
|
||||
static void connectBlocks(VBStyleCollection<BasicBlock*, int>& lstbb,
|
||||
std::unordered_map<int, BasicBlock*>& mapInstrBlocks);
|
||||
void setExceptionEdges(InstructionSequence* instrseq,
|
||||
std::unordered_map<int, BasicBlock*>& instrBlocks);
|
||||
void setSubroutineEdges();
|
||||
void setFirstAndLastBlocks();
|
||||
|
||||
void processJsr();
|
||||
int processJsrRanges();
|
||||
std::unordered_set<BasicBlock*> getJsrRange(BasicBlock* jsr, BasicBlock* ret);
|
||||
void splitJsrRange(BasicBlock* jsr, BasicBlock* ret,
|
||||
const std::unordered_set<BasicBlock*>& common_blocks);
|
||||
void splitJsrExceptionRanges(const std::unordered_set<BasicBlock*>& common_blocks,
|
||||
const std::unordered_map<int, BasicBlock*>& mapNewNodes);
|
||||
void removeJsr(StructClass* cl, StructMethod* mt);
|
||||
static void removeJsrInstructions(class ConstantPool* pool, BasicBlock* block,
|
||||
class DataPoint data);
|
||||
|
||||
static void addToReversePostOrderListIterative(BasicBlock* root,
|
||||
std::vector<BasicBlock*>& lst);
|
||||
|
||||
VBStyleCollection<BasicBlock*, int> blocks_;
|
||||
BasicBlock* first_ = nullptr;
|
||||
BasicBlock* last_ = nullptr;
|
||||
std::vector<ExceptionRangeCFG*> exceptions_;
|
||||
std::unordered_map<BasicBlock*, BasicBlock*> subroutines_;
|
||||
std::unordered_set<BasicBlock*> finallyExits_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class CounterContainer {
|
||||
public:
|
||||
static constexpr int STATEMENT_COUNTER = 0;
|
||||
static constexpr int EXPRESSION_COUNTER = 1;
|
||||
static constexpr int VAR_COUNTER = 2;
|
||||
|
||||
CounterContainer() : values_{1, 1, 1} {}
|
||||
|
||||
void setCounter(int counter, int value) { values_[counter] = value; }
|
||||
int getCounter(int counter) const { return values_[counter]; }
|
||||
int getCounterAndIncrement(int counter) { return values_[counter]++; }
|
||||
|
||||
private:
|
||||
std::array<int32_t, 3> values_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,186 @@
|
||||
#include "DataInputFullStream.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <ios>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constructors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
DataInputFullStream::DataInputFullStream(const std::vector<uint8_t>& bytes)
|
||||
: myData(bytes), myPos(0) {}
|
||||
|
||||
DataInputFullStream::DataInputFullStream(std::vector<uint8_t>&& bytes)
|
||||
: myData(std::move(bytes)), myPos(0) {}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private helper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
uint8_t DataInputFullStream::nextByte() {
|
||||
if (myPos >= myData.size()) {
|
||||
throw std::ios_base::failure("premature end of stream");
|
||||
}
|
||||
return myData[myPos++];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reads
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int8_t DataInputFullStream::readByte() {
|
||||
return static_cast<int8_t>(nextByte());
|
||||
}
|
||||
|
||||
int32_t DataInputFullStream::readUnsignedByte() {
|
||||
return static_cast<int32_t>(nextByte());
|
||||
}
|
||||
|
||||
int16_t DataInputFullStream::readShort() {
|
||||
uint8_t b0 = nextByte();
|
||||
uint8_t b1 = nextByte();
|
||||
return static_cast<int16_t>((static_cast<uint16_t>(b0) << 8) |
|
||||
static_cast<uint16_t>(b1));
|
||||
}
|
||||
|
||||
int32_t DataInputFullStream::readUnsignedShort() {
|
||||
uint8_t b0 = nextByte();
|
||||
uint8_t b1 = nextByte();
|
||||
return static_cast<int32_t>((static_cast<uint32_t>(b0) << 8) |
|
||||
static_cast<uint32_t>(b1));
|
||||
}
|
||||
|
||||
int32_t DataInputFullStream::readInt() {
|
||||
uint8_t b0 = nextByte();
|
||||
uint8_t b1 = nextByte();
|
||||
uint8_t b2 = nextByte();
|
||||
uint8_t b3 = nextByte();
|
||||
uint32_t u = (static_cast<uint32_t>(b0) << 24) |
|
||||
(static_cast<uint32_t>(b1) << 16) |
|
||||
(static_cast<uint32_t>(b2) << 8) |
|
||||
static_cast<uint32_t>(b3);
|
||||
return static_cast<int32_t>(u);
|
||||
}
|
||||
|
||||
int64_t DataInputFullStream::readLong() {
|
||||
uint8_t b[8];
|
||||
for (int i = 0; i < 8; ++i) b[i] = nextByte();
|
||||
uint64_t u = (static_cast<uint64_t>(b[0]) << 56) |
|
||||
(static_cast<uint64_t>(b[1]) << 48) |
|
||||
(static_cast<uint64_t>(b[2]) << 40) |
|
||||
(static_cast<uint64_t>(b[3]) << 32) |
|
||||
(static_cast<uint64_t>(b[4]) << 24) |
|
||||
(static_cast<uint64_t>(b[5]) << 16) |
|
||||
(static_cast<uint64_t>(b[6]) << 8) |
|
||||
static_cast<uint64_t>(b[7]);
|
||||
return static_cast<int64_t>(u);
|
||||
}
|
||||
|
||||
float DataInputFullStream::readFloat() {
|
||||
int32_t bits = readInt();
|
||||
float result;
|
||||
static_assert(sizeof(float) == sizeof(int32_t), "float must be 32-bit");
|
||||
std::memcpy(&result, &bits, sizeof(float));
|
||||
return result;
|
||||
}
|
||||
|
||||
double DataInputFullStream::readDouble() {
|
||||
int64_t bits = readLong();
|
||||
double result;
|
||||
static_assert(sizeof(double) == sizeof(int64_t), "double must be 64-bit");
|
||||
std::memcpy(&result, &bits, sizeof(double));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string DataInputFullStream::readUTF() {
|
||||
// Java modified-UTF-8: 2-byte unsigned length, then that many bytes.
|
||||
int32_t utfLen = readUnsignedShort();
|
||||
if (utfLen < 0) {
|
||||
throw std::ios_base::failure("invalid UTF length");
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result.reserve(static_cast<std::size_t>(utfLen));
|
||||
|
||||
int32_t remaining = utfLen;
|
||||
while (remaining > 0) {
|
||||
uint8_t a = nextByte();
|
||||
--remaining;
|
||||
|
||||
if ((a & 0x80) == 0) {
|
||||
// 1-byte sequence (ASCII)
|
||||
result += static_cast<char>(a);
|
||||
} else if ((a & 0xE0) == 0xC0) {
|
||||
// 2-byte sequence
|
||||
if (remaining < 1) throw std::ios_base::failure("malformed modified-UTF-8");
|
||||
uint8_t b = nextByte();
|
||||
--remaining;
|
||||
if ((b & 0xC0) != 0x80) throw std::ios_base::failure("malformed modified-UTF-8");
|
||||
char32_t cp = ((static_cast<char32_t>(a & 0x1F)) << 6) |
|
||||
(static_cast<char32_t>(b & 0x3F));
|
||||
// Encode back to UTF-8
|
||||
if (cp < 0x80) {
|
||||
result += static_cast<char>(cp);
|
||||
} else {
|
||||
result += static_cast<char>(0xC0 | ((cp >> 6) & 0x1F));
|
||||
result += static_cast<char>(0x80 | (cp & 0x3F));
|
||||
}
|
||||
} else if ((a & 0xF0) == 0xE0) {
|
||||
// 3-byte sequence
|
||||
if (remaining < 2) throw std::ios_base::failure("malformed modified-UTF-8");
|
||||
uint8_t b = nextByte();
|
||||
uint8_t c = nextByte();
|
||||
remaining -= 2;
|
||||
if ((b & 0xC0) != 0x80 || (c & 0xC0) != 0x80) {
|
||||
throw std::ios_base::failure("malformed modified-UTF-8");
|
||||
}
|
||||
char32_t cp = ((static_cast<char32_t>(a & 0x0F)) << 12) |
|
||||
((static_cast<char32_t>(b & 0x3F)) << 6) |
|
||||
(static_cast<char32_t>(c & 0x3F));
|
||||
result += static_cast<char>(0xE0 | ((cp >> 12) & 0x0F));
|
||||
result += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||
result += static_cast<char>(0x80 | (cp & 0x3F));
|
||||
} else {
|
||||
throw std::ios_base::failure("malformed modified-UTF-8");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DataInputFullStream::read(int32_t n) {
|
||||
if (n < 0) throw std::invalid_argument("n must be non-negative");
|
||||
std::size_t ulen = static_cast<std::size_t>(n);
|
||||
if (myPos + ulen > myData.size()) {
|
||||
throw std::ios_base::failure("premature end of stream");
|
||||
}
|
||||
std::vector<uint8_t> result(myData.begin() + static_cast<std::ptrdiff_t>(myPos),
|
||||
myData.begin() + static_cast<std::ptrdiff_t>(myPos + ulen));
|
||||
myPos += ulen;
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataInputFullStream::discard(int32_t n) {
|
||||
if (n < 0) throw std::invalid_argument("n must be non-negative");
|
||||
std::size_t ulen = static_cast<std::size_t>(n);
|
||||
if (myPos + ulen > myData.size()) {
|
||||
throw std::ios_base::failure("premature end of stream");
|
||||
}
|
||||
myPos += ulen;
|
||||
}
|
||||
|
||||
int32_t DataInputFullStream::available() const {
|
||||
return static_cast<int32_t>(myData.size() - myPos);
|
||||
}
|
||||
|
||||
std::size_t DataInputFullStream::position() const {
|
||||
return myPos;
|
||||
}
|
||||
|
||||
void DataInputFullStream::reset() {
|
||||
myPos = 0;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ios>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
/**
|
||||
* Big-endian binary reader backed by an in-memory byte buffer.
|
||||
* Mirrors Java's DataInputStream wrapping a ByteArrayInputStream.
|
||||
*/
|
||||
class DataInputFullStream {
|
||||
public:
|
||||
explicit DataInputFullStream(const std::vector<uint8_t>& bytes);
|
||||
explicit DataInputFullStream(std::vector<uint8_t>&& bytes);
|
||||
|
||||
// --- DataInputStream-compatible reads ---
|
||||
|
||||
// Reads a signed byte (-128..127)
|
||||
int8_t readByte();
|
||||
|
||||
// Reads an unsigned byte (0..255) as int32_t
|
||||
int32_t readUnsignedByte();
|
||||
|
||||
// Reads a big-endian signed 16-bit short
|
||||
int16_t readShort();
|
||||
|
||||
// Reads a big-endian unsigned 16-bit value (0..65535) as int32_t
|
||||
int32_t readUnsignedShort();
|
||||
|
||||
// Reads a big-endian signed 32-bit int
|
||||
int32_t readInt();
|
||||
|
||||
// Reads a big-endian signed 64-bit long
|
||||
int64_t readLong();
|
||||
|
||||
// Reads a big-endian IEEE 754 float
|
||||
float readFloat();
|
||||
|
||||
// Reads a big-endian IEEE 754 double
|
||||
double readDouble();
|
||||
|
||||
// Reads a Java-style modified-UTF-8 string (2-byte length prefix)
|
||||
std::string readUTF();
|
||||
|
||||
// Reads exactly n bytes and returns them as a vector
|
||||
std::vector<uint8_t> read(int32_t n);
|
||||
|
||||
// Skips (discards) exactly n bytes
|
||||
void discard(int32_t n);
|
||||
|
||||
// Returns the number of bytes remaining in the stream
|
||||
int32_t available() const;
|
||||
|
||||
// Returns current read position
|
||||
std::size_t position() const;
|
||||
|
||||
// Resets position to the beginning
|
||||
void reset();
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> myData;
|
||||
std::size_t myPos;
|
||||
|
||||
uint8_t nextByte();
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "DataPoint.h"
|
||||
#include "MethodDescriptor.h"
|
||||
#include "CodeConstants.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ============================================================================
|
||||
// Constructors
|
||||
// ============================================================================
|
||||
DataPoint::DataPoint()
|
||||
: localVariables_()
|
||||
, stack_()
|
||||
{}
|
||||
|
||||
DataPoint::DataPoint(std::vector<VarType> localVariables, ListStack<VarType> stack)
|
||||
: localVariables_(std::move(localVariables))
|
||||
, stack_(std::move(stack))
|
||||
{}
|
||||
|
||||
// ============================================================================
|
||||
// copy
|
||||
// ============================================================================
|
||||
DataPoint DataPoint::copy() const {
|
||||
return DataPoint(localVariables_, stack_.copy());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// setVariable
|
||||
// ============================================================================
|
||||
void DataPoint::setVariable(int32_t index, const VarType& value) {
|
||||
// Grow the vector to accommodate index if needed, filling gaps with NOTINITIALIZED
|
||||
while (static_cast<int32_t>(localVariables_.size()) <= index) {
|
||||
localVariables_.emplace_back(CodeConstants::TYPE_NOTINITIALIZED);
|
||||
}
|
||||
localVariables_[static_cast<size_t>(index)] = value;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// getVariable
|
||||
// ============================================================================
|
||||
VarType DataPoint::getVariable(int32_t index) const {
|
||||
if (index < static_cast<int32_t>(localVariables_.size())) {
|
||||
return localVariables_[static_cast<size_t>(index)];
|
||||
}
|
||||
return VarType(CodeConstants::TYPE_NOTINITIALIZED);
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "VarType.h"
|
||||
#include "ListStack.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
class DataPoint {
|
||||
public:
|
||||
DataPoint();
|
||||
|
||||
// Deep-copy factory.
|
||||
DataPoint copy() const;
|
||||
|
||||
// Set (or grow-and-set) the local variable slot at `index`.
|
||||
void setVariable(int32_t index, const VarType& value);
|
||||
|
||||
// Return the local variable at `index`, or TYPE_NOTINITIALIZED if out of range.
|
||||
VarType getVariable(int32_t index) const;
|
||||
|
||||
ListStack<VarType>& getStack() { return stack_; }
|
||||
const ListStack<VarType>& getStack() const { return stack_; }
|
||||
|
||||
private:
|
||||
explicit DataPoint(std::vector<VarType> localVariables, ListStack<VarType> stack);
|
||||
|
||||
std::vector<VarType> localVariables_;
|
||||
ListStack<VarType> stack_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DeadCodeHelper.h"
|
||||
#include "BasicBlock.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "ControlFlowGraph.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "ExceptionRangeCFG.h"
|
||||
#include "IFernflowerPreferences.h"
|
||||
#include "Instruction.h"
|
||||
#include "InstructionSequence.h"
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
void DeadCodeHelper::removeDeadBlocks(ControlFlowGraph& graph) {
|
||||
std::vector<BasicBlock*> stack;
|
||||
std::unordered_set<BasicBlock*> setStacked;
|
||||
|
||||
stack.push_back(graph.getFirst());
|
||||
setStacked.insert(graph.getFirst());
|
||||
|
||||
while (!stack.empty()) {
|
||||
BasicBlock* block = stack.front();
|
||||
stack.erase(stack.begin());
|
||||
|
||||
std::vector<BasicBlock*> successors = block->getSuccessors();
|
||||
successors.insert(successors.end(), block->getSuccessorExceptions().begin(),
|
||||
block->getSuccessorExceptions().end());
|
||||
|
||||
for (BasicBlock* successor : successors) {
|
||||
if (!setStacked.count(successor)) {
|
||||
stack.push_back(successor);
|
||||
setStacked.insert(successor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BasicBlock*> toRemove;
|
||||
for (int i = 0; i < graph.getBlocks().size(); i++) {
|
||||
BasicBlock* block = graph.getBlocks().get(i);
|
||||
if (!setStacked.count(block)) {
|
||||
toRemove.push_back(block);
|
||||
}
|
||||
}
|
||||
|
||||
for (BasicBlock* block : toRemove) {
|
||||
graph.removeBlock(block);
|
||||
}
|
||||
}
|
||||
|
||||
bool DeadCodeHelper::removeEmptyBlock(ControlFlowGraph& graph, BasicBlock* block, bool merging) {
|
||||
bool deletedRanges = false;
|
||||
|
||||
if (block->getSeq()->isEmpty()) {
|
||||
if (block->getSuccessors().size() > 1) {
|
||||
if (block->getPredecessors().size() > 1) {
|
||||
throw std::runtime_error("ERROR: empty block with multiple predecessors and successors found");
|
||||
} else if (!merging) {
|
||||
throw std::runtime_error("ERROR: empty block with multiple successors found");
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<BasicBlock*> setExits(graph.getLast()->getPredecessors().begin(),
|
||||
graph.getLast()->getPredecessors().end());
|
||||
|
||||
if (block->getPredecessorExceptions().empty() &&
|
||||
(!setExits.count(block) || block->getPredecessors().size() == 1)) {
|
||||
|
||||
if (setExits.count(block)) {
|
||||
BasicBlock* predecessor = block->getPredecessors()[0];
|
||||
Instruction* lastInstr = predecessor->getSeq()->isEmpty() ? nullptr :
|
||||
predecessor->getSeq()->getLastInstr();
|
||||
if (predecessor->getSuccessors().size() != 1 ||
|
||||
(lastInstr && lastInstr->group == CodeConstants::GROUP_SWITCH)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<BasicBlock*> predecessors(block->getPredecessors().begin(),
|
||||
block->getPredecessors().end());
|
||||
std::unordered_set<BasicBlock*> successors(block->getSuccessors().begin(),
|
||||
block->getSuccessors().end());
|
||||
|
||||
// common exception handlers
|
||||
std::unordered_set<BasicBlock*>* setCommonExceptionHandlers = nullptr;
|
||||
std::unordered_set<BasicBlock*> ceh;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto& adjacent = (i == 0) ? predecessors : successors;
|
||||
for (BasicBlock* adj : adjacent) {
|
||||
if (!setCommonExceptionHandlers) {
|
||||
ceh = std::unordered_set<BasicBlock*>(adj->getSuccessorExceptions().begin(),
|
||||
adj->getSuccessorExceptions().end());
|
||||
setCommonExceptionHandlers = &ceh;
|
||||
} else {
|
||||
for (auto it = ceh.begin(); it != ceh.end(); ) {
|
||||
auto& excSucc = adj->getSuccessorExceptions();
|
||||
if (std::find(excSucc.begin(), excSucc.end(), *it) == excSucc.end())
|
||||
it = ceh.erase(it);
|
||||
else ++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setCommonExceptionHandlers && !setCommonExceptionHandlers->empty()) {
|
||||
for (BasicBlock* handler : *setCommonExceptionHandlers) {
|
||||
auto& be = block->getSuccessorExceptions();
|
||||
if (std::find(be.begin(), be.end(), handler) == be.end()) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// remove single-block ranges
|
||||
auto& lstRanges = graph.getExceptions();
|
||||
bool removeEmptyRanges = DecompilerContext::getOption(IFernflowerPreferences::REMOVE_EMPTY_RANGES);
|
||||
for (int i = (int)lstRanges.size() - 1; i >= 0; i--) {
|
||||
ExceptionRangeCFG* range = lstRanges[i];
|
||||
auto& lst = range->getProtectedRange();
|
||||
if (lst.size() == 1 && lst[0] == block) {
|
||||
if (removeEmptyRanges) {
|
||||
block->removeSuccessorException(range->getHandler());
|
||||
lstRanges.erase(lstRanges.begin() + i);
|
||||
deletedRanges = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// connect remaining nodes
|
||||
if (merging) {
|
||||
BasicBlock* predecessor = block->getPredecessors()[0];
|
||||
predecessor->removeSuccessor(block);
|
||||
std::vector<BasicBlock*> sucList = block->getSuccessors();
|
||||
for (BasicBlock* successor : sucList) {
|
||||
block->removeSuccessor(successor);
|
||||
predecessor->addSuccessor(successor);
|
||||
}
|
||||
} else {
|
||||
for (BasicBlock* pred : predecessors) {
|
||||
for (BasicBlock* succ : successors) {
|
||||
pred->replaceSuccessor(block, succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update finally exits
|
||||
auto& setFinallyExits = graph.getFinallyExits();
|
||||
if (setFinallyExits.count(block)) {
|
||||
setFinallyExits.erase(block);
|
||||
if (!predecessors.empty()) {
|
||||
setFinallyExits.insert(*predecessors.begin());
|
||||
}
|
||||
}
|
||||
|
||||
// replace first
|
||||
if (graph.getFirst() == block) {
|
||||
if (successors.size() != 1) {
|
||||
throw std::runtime_error("multiple or no entry blocks!");
|
||||
} else {
|
||||
graph.setFirst(*successors.begin());
|
||||
}
|
||||
}
|
||||
|
||||
graph.removeBlock(block);
|
||||
|
||||
if (deletedRanges) {
|
||||
removeDeadBlocks(graph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deletedRanges;
|
||||
}
|
||||
|
||||
void DeadCodeHelper::removeEmptyBlocks(ControlFlowGraph& graph) {
|
||||
auto& blocks = graph.getBlocks();
|
||||
bool cont;
|
||||
do {
|
||||
cont = false;
|
||||
for (int i = blocks.size() - 1; i >= 0; i--) {
|
||||
BasicBlock* block = blocks.get(i);
|
||||
if (removeEmptyBlock(graph, block, false)) {
|
||||
cont = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (cont);
|
||||
}
|
||||
|
||||
bool DeadCodeHelper::isDominator(ControlFlowGraph& graph, BasicBlock* block, BasicBlock* dom) {
|
||||
if (block == dom) return true;
|
||||
|
||||
std::unordered_set<BasicBlock*> marked;
|
||||
std::vector<BasicBlock*> lstNodes;
|
||||
lstNodes.push_back(block);
|
||||
|
||||
while (!lstNodes.empty()) {
|
||||
BasicBlock* node = lstNodes.front();
|
||||
lstNodes.erase(lstNodes.begin());
|
||||
|
||||
if (marked.count(node)) continue;
|
||||
marked.insert(node);
|
||||
|
||||
if (node == graph.getFirst()) return false;
|
||||
|
||||
for (BasicBlock* pred : node->getPredecessors()) {
|
||||
if (pred != dom && !marked.count(pred)) lstNodes.push_back(pred);
|
||||
}
|
||||
for (BasicBlock* pred : node->getPredecessorExceptions()) {
|
||||
if (pred != dom && !marked.count(pred)) lstNodes.push_back(pred);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
namespace ff {
|
||||
|
||||
class BasicBlock;
|
||||
class ControlFlowGraph;
|
||||
|
||||
class DeadCodeHelper {
|
||||
public:
|
||||
static void removeDeadBlocks(ControlFlowGraph& graph);
|
||||
static void removeEmptyBlocks(ControlFlowGraph& graph);
|
||||
static bool isDominator(ControlFlowGraph& graph, BasicBlock* block, BasicBlock* dom);
|
||||
|
||||
private:
|
||||
static bool removeEmptyBlock(ControlFlowGraph& graph, BasicBlock* block, bool merging);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DecHelper.h"
|
||||
#include "StatEdge.h"
|
||||
#include "Statement.h"
|
||||
#include "Exprent.h"
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ff {
|
||||
|
||||
bool DecHelper::checkStatementExceptions(const std::vector<Statement*>& lst) {
|
||||
if (lst.empty()) return true;
|
||||
|
||||
std::unordered_set<Statement*> all(lst.begin(), lst.end());
|
||||
std::unordered_set<Statement*> handlers;
|
||||
|
||||
// Use pointer to hold current intersection (null means first iteration)
|
||||
std::unordered_set<Statement*>* intersection = nullptr;
|
||||
|
||||
for (Statement* stat : lst) {
|
||||
auto setNew = stat->getNeighboursSet(StatEdge::EdgeType::EXCEPTION, StatEdge::EdgeDirection::FORWARD);
|
||||
|
||||
if (intersection == nullptr) {
|
||||
intersection = new std::unordered_set<Statement*>(setNew);
|
||||
} else {
|
||||
// interclone = intersection \ setNew
|
||||
std::unordered_set<Statement*> interclone;
|
||||
for (auto* s : *intersection) {
|
||||
if (!setNew.count(s)) interclone.insert(s);
|
||||
}
|
||||
// intersection = intersection ∩ setNew
|
||||
for (auto it = intersection->begin(); it != intersection->end(); ) {
|
||||
if (!setNew.count(*it)) it = intersection->erase(it);
|
||||
else ++it;
|
||||
}
|
||||
// setNew = setNew \ intersection
|
||||
for (auto* s : *intersection) setNew.erase(s);
|
||||
|
||||
for (auto* s : interclone) handlers.insert(s);
|
||||
for (auto* s : setNew) handlers.insert(s);
|
||||
}
|
||||
}
|
||||
|
||||
delete intersection;
|
||||
|
||||
for (Statement* stat : handlers) {
|
||||
if (!all.count(stat)) return false;
|
||||
auto backward = stat->getNeighbours(StatEdge::EdgeType::EXCEPTION, StatEdge::EdgeDirection::BACKWARD);
|
||||
for (Statement* s : backward) {
|
||||
if (!all.count(s)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for other handlers (excluding head, i.e. index 0)
|
||||
for (int i = 1; i < (int)lst.size(); i++) {
|
||||
Statement* stat = lst[i];
|
||||
if (!stat->getPredecessorEdges(StatEdge::EdgeType::EXCEPTION).empty() && !handlers.count(stat)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Statement* findIfHead(Statement* head) {
|
||||
while (head != nullptr && head->type != Statement::StatementType::IF) {
|
||||
if (head->type != Statement::StatementType::SEQUENCE) {
|
||||
return nullptr;
|
||||
}
|
||||
head = head->getFirst();
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
void DecHelper::processStatement(Statement* root, Statement* parent) {
|
||||
// processStatement in Java iterates child statements and processes them.
|
||||
// In the Java source, this is used to propagate context; provide a no-op stub
|
||||
// that satisfies compilation while callers exist.
|
||||
(void)root; (void)parent;
|
||||
}
|
||||
|
||||
bool DecHelper::findExtendedIf(Statement* statement) {
|
||||
// Java: checks whether the statement is the head of an if-chain that can be
|
||||
// extended into an if/else-if chain. Return false conservatively.
|
||||
(void)statement;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Statement;
|
||||
|
||||
class DecHelper {
|
||||
public:
|
||||
static bool checkStatementExceptions(const std::vector<Statement*>& lst);
|
||||
static void processStatement(Statement* root, Statement* parent);
|
||||
static bool findExtendedIf(Statement* statement);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DecompilerContext.h"
|
||||
#include "LimitContainer.h"
|
||||
#include "BytecodeSourceMapper.h"
|
||||
#include "ImportCollector.h"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// Thread-local current context (mirrors Java's ThreadLocal<DecompilerContext>).
|
||||
thread_local static DecompilerContext* tl_currentContext = nullptr;
|
||||
|
||||
DecompilerContext::DecompilerContext(
|
||||
const std::unordered_map<std::string, std::string>& properties,
|
||||
IFernflowerLogger* logger,
|
||||
StructContext* structContext,
|
||||
ClassesProcessor* classProcessor,
|
||||
PoolInterceptor* interceptor,
|
||||
CancellationManager* cancellationManager,
|
||||
std::shared_ptr<IVariableNamingFactory> renamerFactory)
|
||||
: properties_(properties)
|
||||
, logger_(logger)
|
||||
, structContext_(structContext)
|
||||
, classProcessor_(classProcessor)
|
||||
, poolInterceptor_(interceptor)
|
||||
, renamerFactory_(std::move(renamerFactory))
|
||||
, limitsContainer_(std::make_unique<LimitContainer>(properties)) {
|
||||
if (cancellationManager != nullptr) {
|
||||
cancellationManager_ = cancellationManager;
|
||||
} else {
|
||||
auto it = properties.find(IFernflowerPreferences::MAX_PROCESSING_METHOD);
|
||||
int timeout = 0;
|
||||
if (it != properties.end()) {
|
||||
try { timeout = std::stoi(it->second); } catch (...) {}
|
||||
}
|
||||
ownedCancellationManager_ = CancellationManager::getSimpleWithTimeout(timeout);
|
||||
cancellationManager_ = ownedCancellationManager_.get();
|
||||
}
|
||||
}
|
||||
|
||||
DecompilerContext::~DecompilerContext() = default;
|
||||
|
||||
DecompilerContext* DecompilerContext::getCurrentContext() {
|
||||
return tl_currentContext;
|
||||
}
|
||||
|
||||
void DecompilerContext::setCurrentContext(DecompilerContext* context) {
|
||||
tl_currentContext = context;
|
||||
}
|
||||
|
||||
void DecompilerContext::setProperty(const std::string& key, const std::string& value) {
|
||||
getCurrentContext()->properties_[key] = value;
|
||||
}
|
||||
|
||||
void DecompilerContext::startClass(ImportCollector* importCollector) {
|
||||
auto* ctx = getCurrentContext();
|
||||
ctx->importCollector_ = importCollector;
|
||||
ctx->counterContainer_ = CounterContainer{};
|
||||
ctx->bytecodeSourceMapper_ = new BytecodeSourceMapper();
|
||||
}
|
||||
|
||||
void DecompilerContext::startMethod(VarProcessor* varProcessor) {
|
||||
auto* ctx = getCurrentContext();
|
||||
ctx->varProcessor_ = varProcessor;
|
||||
ctx->counterContainer_ = CounterContainer{};
|
||||
}
|
||||
|
||||
std::string DecompilerContext::getProperty(const std::string& key) {
|
||||
auto& props = getCurrentContext()->properties_;
|
||||
auto it = props.find(key);
|
||||
return (it != props.end()) ? it->second : "";
|
||||
}
|
||||
|
||||
bool DecompilerContext::getOption(const std::string& key) {
|
||||
return getProperty(key) == "1";
|
||||
}
|
||||
|
||||
std::string DecompilerContext::getNewLineSeparator() {
|
||||
return getOption(IFernflowerPreferences::NEW_LINE_SEPARATOR)
|
||||
? IFernflowerPreferences::LINE_SEPARATOR_UNX
|
||||
: IFernflowerPreferences::LINE_SEPARATOR_WIN;
|
||||
}
|
||||
|
||||
IFernflowerLogger* DecompilerContext::getLogger() { return getCurrentContext()->logger_; }
|
||||
LimitContainer* DecompilerContext::getLimitContainer() { return getCurrentContext()->limitsContainer_.get(); }
|
||||
StructContext* DecompilerContext::getStructContext() { return getCurrentContext()->structContext_; }
|
||||
ClassesProcessor* DecompilerContext::getClassProcessor() { return getCurrentContext()->classProcessor_; }
|
||||
CancellationManager* DecompilerContext::getCancellationManager() { return getCurrentContext()->cancellationManager_; }
|
||||
PoolInterceptor* DecompilerContext::getPoolInterceptor() { return getCurrentContext()->poolInterceptor_; }
|
||||
IVariableNamingFactory* DecompilerContext::getNamingFactory() { return getCurrentContext()->renamerFactory_.get(); }
|
||||
ImportCollector* DecompilerContext::getImportCollector() { return getCurrentContext()->importCollector_; }
|
||||
VarProcessor* DecompilerContext::getVarProcessor() { return getCurrentContext()->varProcessor_; }
|
||||
CounterContainer* DecompilerContext::getCounterContainer() { return &getCurrentContext()->counterContainer_; }
|
||||
BytecodeSourceMapper* DecompilerContext::getBytecodeSourceMapper() { return getCurrentContext()->bytecodeSourceMapper_; }
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "IFernflowerLogger.h"
|
||||
#include "IFernflowerPreferences.h"
|
||||
#include "IVariableNamingFactory.h"
|
||||
#include "CancellationManager.h"
|
||||
#include "CounterContainer.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class StructContext;
|
||||
class ClassesProcessor;
|
||||
class PoolInterceptor;
|
||||
class ImportCollector;
|
||||
class VarProcessor;
|
||||
class BytecodeSourceMapper;
|
||||
class LimitContainer;
|
||||
|
||||
class DecompilerContext {
|
||||
public:
|
||||
static constexpr const char* CURRENT_CLASS = "CURRENT_CLASS";
|
||||
static constexpr const char* CURRENT_CLASS_WRAPPER = "CURRENT_CLASS_WRAPPER";
|
||||
static constexpr const char* CURRENT_CLASS_NODE = "CURRENT_CLASS_NODE";
|
||||
static constexpr const char* CURRENT_METHOD_WRAPPER = "CURRENT_METHOD_WRAPPER";
|
||||
static constexpr const char* CURRENT_VAR_PROCESSOR = "CURRENT_VAR_PROCESSOR";
|
||||
static constexpr const char* IN_CLASS_TYPE_PARAMS = "IN_CLASS_TYPE_PARAMS";
|
||||
static constexpr const char* RENAMER_FACTORY = "RENAMER_FACTORY";
|
||||
|
||||
DecompilerContext(const std::unordered_map<std::string, std::string>& properties,
|
||||
IFernflowerLogger* logger,
|
||||
StructContext* structContext,
|
||||
ClassesProcessor* classProcessor,
|
||||
PoolInterceptor* interceptor,
|
||||
CancellationManager* cancellationManager,
|
||||
std::shared_ptr<IVariableNamingFactory> renamerFactory);
|
||||
|
||||
~DecompilerContext();
|
||||
|
||||
// Thread-local context management
|
||||
static DecompilerContext* getCurrentContext();
|
||||
static void setCurrentContext(DecompilerContext* context);
|
||||
|
||||
static void setProperty(const std::string& key, const std::string& value);
|
||||
|
||||
static void startClass(ImportCollector* importCollector);
|
||||
static void startMethod(VarProcessor* varProcessor);
|
||||
|
||||
static std::string getProperty(const std::string& key);
|
||||
static bool getOption(const std::string& key);
|
||||
static std::string getNewLineSeparator();
|
||||
|
||||
static IFernflowerLogger* getLogger();
|
||||
static LimitContainer* getLimitContainer();
|
||||
static StructContext* getStructContext();
|
||||
static ClassesProcessor* getClassProcessor();
|
||||
static CancellationManager* getCancellationManager();
|
||||
static PoolInterceptor* getPoolInterceptor();
|
||||
static IVariableNamingFactory* getNamingFactory();
|
||||
static ImportCollector* getImportCollector();
|
||||
static VarProcessor* getVarProcessor();
|
||||
static CounterContainer* getCounterContainer();
|
||||
static BytecodeSourceMapper* getBytecodeSourceMapper();
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> properties_;
|
||||
IFernflowerLogger* logger_;
|
||||
StructContext* structContext_;
|
||||
ClassesProcessor* classProcessor_;
|
||||
PoolInterceptor* poolInterceptor_;
|
||||
std::unique_ptr<CancellationManager> ownedCancellationManager_;
|
||||
CancellationManager* cancellationManager_;
|
||||
std::shared_ptr<IVariableNamingFactory> renamerFactory_;
|
||||
CounterContainer counterContainer_;
|
||||
std::unique_ptr<LimitContainer> limitsContainer_;
|
||||
ImportCollector* importCollector_ = nullptr;
|
||||
VarProcessor* varProcessor_ = nullptr;
|
||||
BytecodeSourceMapper* bytecodeSourceMapper_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DirectGraph.h"
|
||||
#include "CancellationManager.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "DirectNode.h"
|
||||
#include "Exprent.h"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ff {
|
||||
|
||||
void DirectGraph::sortReversePostOrder() {
|
||||
std::vector<DirectNode*> res;
|
||||
addToReversePostOrderListIterative(first, res);
|
||||
|
||||
nodes.clear();
|
||||
for (DirectNode* node : res) {
|
||||
nodes.addWithKey(node, node->id);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectGraph::addToReversePostOrderListIterative(DirectNode* root, std::vector<DirectNode*>& lst) {
|
||||
std::vector<DirectNode*> stackNode;
|
||||
std::vector<int> stackIndex;
|
||||
std::unordered_set<DirectNode*> setVisited; // need include for this
|
||||
|
||||
stackNode.push_back(root);
|
||||
stackIndex.push_back(0);
|
||||
|
||||
while (!stackNode.empty()) {
|
||||
DirectNode* node = stackNode.back();
|
||||
int index = stackIndex.back();
|
||||
stackIndex.pop_back();
|
||||
|
||||
setVisited.insert(node);
|
||||
|
||||
bool pushed = false;
|
||||
for (; index < (int)node->successors.size(); index++) {
|
||||
DirectNode* succ = node->successors[index];
|
||||
if (!setVisited.count(succ)) {
|
||||
stackIndex.push_back(index + 1);
|
||||
stackNode.push_back(succ);
|
||||
stackIndex.push_back(0);
|
||||
pushed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushed) {
|
||||
lst.insert(lst.begin(), node);
|
||||
stackNode.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectGraph::iterateExprents(ExprentIterator iter) {
|
||||
auto* cm = DecompilerContext::getCancellationManager();
|
||||
|
||||
std::vector<DirectNode*> stack;
|
||||
stack.push_back(first);
|
||||
|
||||
std::unordered_set<DirectNode*> setVisited;
|
||||
|
||||
while (!stack.empty()) {
|
||||
DirectNode* node = stack.front();
|
||||
stack.erase(stack.begin());
|
||||
|
||||
if (setVisited.count(node)) continue;
|
||||
setVisited.insert(node);
|
||||
|
||||
for (int i = 0; i < (int)node->exprents.size(); i++) {
|
||||
if (cm) cm->checkCanceled();
|
||||
int res = iter(node->exprents[i].get());
|
||||
if (res == 1) return false;
|
||||
if (res == 2) {
|
||||
node->exprents.erase(node->exprents.begin() + i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
stack.insert(stack.end(), node->successors.begin(), node->successors.end());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectGraph::iterateExprentsDeep(ExprentIterator itr) {
|
||||
return iterateExprents([&](Exprent* exprent) -> int {
|
||||
auto lst = exprent->getAllExprents(true);
|
||||
lst.push_back(exprent);
|
||||
for (Exprent* expr : lst) {
|
||||
int res = itr(expr);
|
||||
if (res == 1 || res == 2) return res;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "VBStyleCollection.h"
|
||||
#include "FlattenStatementsHelper.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class DirectNode;
|
||||
class Exprent;
|
||||
|
||||
class DirectGraph {
|
||||
public:
|
||||
VBStyleCollection<DirectNode*, std::string> nodes;
|
||||
DirectNode* first = nullptr;
|
||||
|
||||
// exit -> [source, destination]
|
||||
std::unordered_map<std::string, std::vector<FlattenStatementsHelper::FinallyPathWrapper>> mapShortRangeFinallyPaths;
|
||||
std::unordered_map<std::string, std::vector<FlattenStatementsHelper::FinallyPathWrapper>> mapLongRangeFinallyPaths;
|
||||
|
||||
// negative if branches
|
||||
std::unordered_map<std::string, std::string> mapNegIfBranch;
|
||||
|
||||
// monitor exception paths
|
||||
std::unordered_map<std::string, std::string> mapFinallyMonitorExceptionPathExits;
|
||||
|
||||
void sortReversePostOrder();
|
||||
|
||||
using ExprentIterator = std::function<int(Exprent*)>;
|
||||
bool iterateExprents(ExprentIterator iter);
|
||||
bool iterateExprentsDeep(ExprentIterator itr);
|
||||
|
||||
private:
|
||||
static void addToReversePostOrderListIterative(DirectNode* root, std::vector<DirectNode*>& lst);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DirectNode.h"
|
||||
#include "BasicBlockStatement.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
DirectNode::DirectNode(DirectNodeType t, Statement* stmt, const std::string& idStr)
|
||||
: type(t), id(idStr), statement(stmt), block(nullptr) {}
|
||||
|
||||
DirectNode::DirectNode(DirectNodeType t, Statement* stmt, BasicBlockStatement* blk)
|
||||
: type(t), id(std::to_string(blk->id)), statement(stmt), block(blk) {}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class BasicBlockStatement;
|
||||
class Exprent;
|
||||
class Statement;
|
||||
|
||||
class DirectNode {
|
||||
public:
|
||||
enum class DirectNodeType {
|
||||
DIRECT,
|
||||
TAIL,
|
||||
INIT,
|
||||
CONDITION,
|
||||
INCREMENT,
|
||||
TRY
|
||||
};
|
||||
|
||||
const DirectNodeType type;
|
||||
const std::string id;
|
||||
Statement* statement;
|
||||
BasicBlockStatement* block;
|
||||
|
||||
std::vector<DirectNode*> successors;
|
||||
std::vector<DirectNode*> predecessors;
|
||||
std::vector<std::shared_ptr<Exprent>> exprents;
|
||||
|
||||
DirectNode(DirectNodeType type, Statement* statement, const std::string& id);
|
||||
DirectNode(DirectNodeType type, Statement* statement, BasicBlockStatement* block);
|
||||
|
||||
std::string toString() const { return id; }
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DoStatement.h"
|
||||
#include "StatEdge.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "Exprent.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
DoStatement::DoStatement() : Statement(StatementType::DO) {}
|
||||
|
||||
DoStatement* DoStatement::create(Statement* head) {
|
||||
auto* stat = new DoStatement();
|
||||
stat->first_ = head;
|
||||
stat->stats.addWithKey(head, head->id);
|
||||
// post is always null for do-loops
|
||||
return stat;
|
||||
}
|
||||
|
||||
Statement* DoStatement::isHead(Statement* head) {
|
||||
if (head->getLastBasicType() == StatementType::GENERAL && !head->isMonitorEnter()) {
|
||||
// At most one outgoing edge
|
||||
std::shared_ptr<StatEdge> edge;
|
||||
auto successorEdges = head->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
if (!successorEdges.empty()) {
|
||||
edge = successorEdges[0];
|
||||
}
|
||||
|
||||
// Regular self-loop
|
||||
if (edge && edge->getType() == StatEdge::EdgeType::REGULAR &&
|
||||
edge->getDestination() == head) {
|
||||
return DoStatement::create(head);
|
||||
}
|
||||
|
||||
// Continues: head type != DO and no regular forward edge, but has continue back to basichead
|
||||
if (head->type != StatementType::DO &&
|
||||
(edge == nullptr || edge->getType() != StatEdge::EdgeType::REGULAR)) {
|
||||
Statement* bhead = head->getBasichead();
|
||||
const auto& contSet = head->getContinueSet();
|
||||
if (contSet.count(bhead)) {
|
||||
return DoStatement::create(head);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TextBuffer* DoStatement::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
// Simplified stub — full implementation requires ExprProcessor::listToJava and jmpWrapper
|
||||
return new TextBuffer();
|
||||
}
|
||||
|
||||
Statement* DoStatement::getSimpleCopy() {
|
||||
return new DoStatement();
|
||||
}
|
||||
|
||||
void DoStatement::initSimpleCopy() {
|
||||
Statement::initSimpleCopy();
|
||||
}
|
||||
|
||||
void DoStatement::initExprents() {}
|
||||
void DoStatement::replaceExprent(Exprent* oldexpr, Exprent* newexpr) {
|
||||
if (initExprent_ == oldexpr) initExprent_ = newexpr;
|
||||
if (conditionExprent_ == oldexpr) conditionExprent_ = newexpr;
|
||||
if (incExprent_ == oldexpr) incExprent_ = newexpr;
|
||||
}
|
||||
|
||||
std::vector<IMatchable*> DoStatement::getSequentialObjects() {
|
||||
return Statement::getSequentialObjects();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Statement.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Exprent;
|
||||
|
||||
class DoStatement : public Statement {
|
||||
public:
|
||||
enum class LoopType {
|
||||
DO,
|
||||
WHILE,
|
||||
DO_WHILE,
|
||||
FOR,
|
||||
FOR_EACH
|
||||
};
|
||||
|
||||
DoStatement();
|
||||
|
||||
static DoStatement* create(Statement* head);
|
||||
static Statement* isHead(Statement* head);
|
||||
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
Statement* getSimpleCopy() override;
|
||||
void initSimpleCopy() override;
|
||||
void initExprents() override;
|
||||
void replaceExprent(Exprent* oldexpr, Exprent* newexpr) override;
|
||||
std::vector<IMatchable*> getSequentialObjects() override;
|
||||
|
||||
LoopType getLoopType() const { return loopType_; }
|
||||
void setLoopType(LoopType t) { loopType_ = t; }
|
||||
|
||||
Exprent* getInitExprent() const { return initExprent_; }
|
||||
Exprent* getConditionExprent() const { return conditionExprent_; }
|
||||
Exprent* getIncExprent() const { return incExprent_; }
|
||||
void setInitExprent(Exprent* e) { initExprent_ = e; }
|
||||
void setConditionExprent(Exprent* e) { conditionExprent_ = e; }
|
||||
void setIncExprent(Exprent* e) { incExprent_ = e; }
|
||||
|
||||
private:
|
||||
LoopType loopType_ = LoopType::DO;
|
||||
Exprent* initExprent_ = nullptr;
|
||||
Exprent* conditionExprent_ = nullptr;
|
||||
Exprent* incExprent_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DotExporter.h"
|
||||
#include "BasicBlock.h"
|
||||
#include "ControlFlowGraph.h"
|
||||
#include "DirectGraph.h"
|
||||
#include "DirectNode.h"
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
namespace ff {
|
||||
|
||||
static std::string directBlockIdToDot(const std::string& id) {
|
||||
std::string result = id;
|
||||
// Replace special suffixes with numeric equivalents
|
||||
auto replaceAll = [&](const std::string& from, const std::string& to) {
|
||||
size_t pos = 0;
|
||||
while ((pos = result.find(from, pos)) != std::string::npos) {
|
||||
result.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
};
|
||||
replaceAll("_try", "999");
|
||||
replaceAll("_tail", "888");
|
||||
replaceAll("_init", "111");
|
||||
replaceAll("_cond", "222");
|
||||
replaceAll("_inc", "333");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string DotExporter::toDotFormat(const ControlFlowGraph& graph) {
|
||||
std::ostringstream buf;
|
||||
const std::string nl = "\r\n";
|
||||
|
||||
buf << "digraph G {" << nl;
|
||||
|
||||
const auto& blocks = graph.getBlocks();
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
BasicBlock* block = blocks.get(i);
|
||||
|
||||
// Escape label: replace newlines with \l for DOT
|
||||
std::string label = block->toString();
|
||||
{
|
||||
std::string escaped;
|
||||
for (char c : label) {
|
||||
if (c == '\n' || c == '\r') escaped += "\\l";
|
||||
else if (c == '"') escaped += "\\\"";
|
||||
else escaped += c;
|
||||
}
|
||||
label = escaped;
|
||||
}
|
||||
|
||||
buf << block->id << "[" << nl
|
||||
<< " shape=box" << nl
|
||||
<< " label=\"" << label << "\"" << nl
|
||||
<< "]" << nl;
|
||||
|
||||
// Unique successors
|
||||
std::vector<BasicBlock*> succs = block->getSuccessors();
|
||||
std::unordered_set<int> seen;
|
||||
for (BasicBlock* succ : succs) {
|
||||
if (seen.insert(succ->id).second) {
|
||||
buf << block->id << " -> " << succ->id << ";" << nl;
|
||||
}
|
||||
}
|
||||
|
||||
// Unique exception successors
|
||||
seen.clear();
|
||||
for (BasicBlock* succ : block->getSuccessorExceptions()) {
|
||||
if (seen.insert(succ->id).second) {
|
||||
buf << block->id << " -> " << succ->id << " [style=dotted];" << nl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf << "}";
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
std::string DotExporter::toDotFormat(const DirectGraph& graph) {
|
||||
std::ostringstream buf;
|
||||
|
||||
buf << "digraph G {\r\n";
|
||||
|
||||
for (int i = 0; i < graph.nodes.size(); i++) {
|
||||
DirectNode* block = graph.nodes.get(i);
|
||||
|
||||
std::string dotId = directBlockIdToDot(block->id);
|
||||
|
||||
buf << dotId << " [shape=box,label=\"" << block->id << "\"];\r\n";
|
||||
|
||||
for (DirectNode* dest : block->successors) {
|
||||
buf << dotId << "->" << directBlockIdToDot(dest->id) << ";\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
buf << "}";
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class ControlFlowGraph;
|
||||
class DirectGraph;
|
||||
|
||||
class DotExporter {
|
||||
public:
|
||||
static std::string toDotFormat(const ControlFlowGraph& graph);
|
||||
static std::string toDotFormat(const DirectGraph& graph);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "DummyExitStatement.h"
|
||||
#include "TextBuffer.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
TextBuffer* DummyExitStatement::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
return new TextBuffer();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Statement.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class DummyExitStatement : public Statement {
|
||||
public:
|
||||
uint64_t bytecode = 0;
|
||||
|
||||
DummyExitStatement() : Statement(StatementType::DUMMY_EXIT) {}
|
||||
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
Statement* getSimpleCopy() override { return new DummyExitStatement(); }
|
||||
StartEndPair getStartEndRange() { return StartEndPair(0, 0); }
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ExceptionHandler.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
std::string ExceptionHandler::toString() const {
|
||||
return "from: " + std::to_string(from) +
|
||||
" to: " + std::to_string(to) +
|
||||
" handler: " + std::to_string(handler) + "\n" +
|
||||
"from_instr: " + std::to_string(from_instr) +
|
||||
" to_instr: " + std::to_string(to_instr) +
|
||||
" handler_instr: " + std::to_string(handler_instr) + "\n" +
|
||||
"exceptionClass: " + exceptionClass + "\n";
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
struct ExceptionHandler {
|
||||
int from = 0;
|
||||
int to = 0;
|
||||
int handler = 0;
|
||||
|
||||
int from_instr = 0;
|
||||
int to_instr = 0;
|
||||
int handler_instr = 0;
|
||||
|
||||
std::string exceptionClass; // empty == catch-all
|
||||
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ExceptionRangeCFG.h"
|
||||
#include "BasicBlock.h"
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace ff {
|
||||
|
||||
ExceptionRangeCFG::ExceptionRangeCFG(std::vector<BasicBlock*> protectedRange,
|
||||
BasicBlock* handler,
|
||||
std::vector<std::string>* exceptionTypes)
|
||||
: protectedRange_(std::move(protectedRange))
|
||||
, handler_(handler)
|
||||
, exceptionTypes_(exceptionTypes ? std::make_optional(*exceptionTypes) : std::nullopt) {}
|
||||
|
||||
bool ExceptionRangeCFG::isCircular() const {
|
||||
return std::find(protectedRange_.begin(), protectedRange_.end(), handler_) != protectedRange_.end();
|
||||
}
|
||||
|
||||
std::string ExceptionRangeCFG::toString() const {
|
||||
std::ostringstream buf;
|
||||
buf << "exceptionType:";
|
||||
if (!exceptionTypes_) {
|
||||
buf << " null";
|
||||
} else {
|
||||
for (const auto& t : *exceptionTypes_) buf << " " << t;
|
||||
}
|
||||
buf << "\nhandler: " << handler_->id << "\nrange: ";
|
||||
for (auto* b : protectedRange_) buf << b->id << " ";
|
||||
buf << "\n";
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
void ExceptionRangeCFG::addExceptionType(const std::string& exceptionType) {
|
||||
if (!exceptionTypes_) return;
|
||||
if (exceptionType.empty()) {
|
||||
exceptionTypes_ = std::nullopt;
|
||||
} else {
|
||||
exceptionTypes_->push_back(exceptionType);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ExceptionRangeCFG::getUniqueExceptionsString() const {
|
||||
if (!exceptionTypes_) return {};
|
||||
std::vector<std::string> uniq;
|
||||
for (const auto& t : *exceptionTypes_) {
|
||||
if (std::find(uniq.begin(), uniq.end(), t) == uniq.end()) uniq.push_back(t);
|
||||
}
|
||||
std::string result;
|
||||
for (size_t i = 0; i < uniq.size(); ++i) {
|
||||
if (i) result += ":";
|
||||
result += uniq[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class BasicBlock;
|
||||
|
||||
class ExceptionRangeCFG {
|
||||
public:
|
||||
ExceptionRangeCFG(std::vector<BasicBlock*> protectedRange,
|
||||
BasicBlock* handler,
|
||||
std::vector<std::string>* exceptionTypes);
|
||||
|
||||
bool isCircular() const;
|
||||
std::string toString() const;
|
||||
|
||||
BasicBlock* getHandler() const { return handler_; }
|
||||
void setHandler(BasicBlock* h) { handler_ = h; }
|
||||
std::vector<BasicBlock*>& getProtectedRange() { return protectedRange_; }
|
||||
const std::vector<BasicBlock*>& getProtectedRange() const { return protectedRange_; }
|
||||
std::vector<std::string>* getExceptionTypes() { return exceptionTypes_ ? &*exceptionTypes_ : nullptr; }
|
||||
const std::vector<std::string>* getExceptionTypes() const { return exceptionTypes_ ? &*exceptionTypes_ : nullptr; }
|
||||
|
||||
void addExceptionType(const std::string& exceptionType);
|
||||
std::string getUniqueExceptionsString() const;
|
||||
|
||||
private:
|
||||
std::vector<BasicBlock*> protectedRange_;
|
||||
BasicBlock* handler_;
|
||||
std::optional<std::vector<std::string>> exceptionTypes_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ExceptionTable.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
const ExceptionTable ExceptionTable::EMPTY{{}};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ExceptionHandler.h"
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class ExceptionTable {
|
||||
public:
|
||||
static const ExceptionTable EMPTY;
|
||||
|
||||
explicit ExceptionTable(std::vector<ExceptionHandler> handlers)
|
||||
: handlers_(std::move(handlers)) {}
|
||||
|
||||
const std::vector<ExceptionHandler>& getHandlers() const { return handlers_; }
|
||||
|
||||
private:
|
||||
std::vector<ExceptionHandler> handlers_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ExitExprent.h"
|
||||
#include "CheckTypesResult.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "MatchNode.h"
|
||||
#include "MatchEngine.h"
|
||||
#include "IMatchable.h"
|
||||
#include "CodeConstants.h"
|
||||
#include <variant>
|
||||
|
||||
namespace ff {
|
||||
|
||||
ExitExprent::ExitExprent(int exitType, Exprent* value, VarType retType,
|
||||
uint64_t bytecodeOffsets, MethodDescriptor* methodDescriptor)
|
||||
: Exprent(EXPRENT_EXIT)
|
||||
, exitType_(exitType)
|
||||
, value_(value)
|
||||
, retType_(std::move(retType))
|
||||
, methodDescriptor_(methodDescriptor)
|
||||
{
|
||||
bytecode = bytecodeOffsets;
|
||||
}
|
||||
|
||||
CheckTypesResult* ExitExprent::checkExprTypeBounds() {
|
||||
CheckTypesResult* result = new CheckTypesResult();
|
||||
if (exitType_ == EXIT_RETURN && retType_.getType() != CodeConstants::TYPE_VOID) {
|
||||
result->addMinTypeExprent(value_, VarType::getMinTypeInFamily(retType_.getTypeFamily()));
|
||||
result->addMaxTypeExprent(value_, retType_);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Exprent*> ExitExprent::getAllExprents() const {
|
||||
if (value_) return { value_ };
|
||||
return {};
|
||||
}
|
||||
|
||||
Exprent* ExitExprent::copy() const {
|
||||
return new ExitExprent(exitType_, value_ ? value_->copy() : nullptr,
|
||||
retType_, bytecode, methodDescriptor_);
|
||||
}
|
||||
|
||||
TextBuffer* ExitExprent::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
if (tracer) tracer->addMapping(bytecode);
|
||||
auto* buffer = new TextBuffer();
|
||||
if (exitType_ == EXIT_RETURN) {
|
||||
buffer->append("return");
|
||||
if (retType_.getType() != CodeConstants::TYPE_VOID && value_) {
|
||||
buffer->append(' ');
|
||||
TextBuffer* valBuf = value_->toJava(indent, tracer);
|
||||
buffer->append(valBuf->toString());
|
||||
delete valBuf;
|
||||
}
|
||||
} else {
|
||||
buffer->append("throw ");
|
||||
if (value_) {
|
||||
TextBuffer* valBuf = value_->toJava(indent, tracer);
|
||||
buffer->append(valBuf->toString());
|
||||
delete valBuf;
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ExitExprent::replaceExprent(Exprent* oldExpr, Exprent* newExpr) {
|
||||
if (oldExpr == value_) value_ = newExpr;
|
||||
}
|
||||
|
||||
bool ExitExprent::equals(const Exprent* o) const {
|
||||
if (o == this) return true;
|
||||
auto* et = dynamic_cast<const ExitExprent*>(o);
|
||||
if (!et) return false;
|
||||
if (exitType_ != et->exitType_) return false;
|
||||
if (value_ == nullptr && et->value_ == nullptr) return true;
|
||||
if (value_ == nullptr || et->value_ == nullptr) return false;
|
||||
return value_->equals(et->value_);
|
||||
}
|
||||
|
||||
void ExitExprent::fillBytecodeRange(uint64_t* values) const {
|
||||
if (value_) measureBytecode(values, value_);
|
||||
measureBytecode(values);
|
||||
}
|
||||
|
||||
bool ExitExprent::match(MatchNode* matchNode, MatchEngine* engine) {
|
||||
auto rule = matchNode->getRuleValue(IMatchable::MatchProperties::EXPRENT_EXITTYPE);
|
||||
if (rule.has_value()) {
|
||||
int expected = std::get<int>(rule->value);
|
||||
return exitType_ == expected;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Exprent.h"
|
||||
#include "VarType.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
class MethodDescriptor;
|
||||
class CheckTypesResult;
|
||||
|
||||
class ExitExprent : public Exprent {
|
||||
public:
|
||||
static constexpr int EXIT_RETURN = 0;
|
||||
static constexpr int EXIT_THROW = 1;
|
||||
|
||||
ExitExprent(int exitType, Exprent* value, VarType retType, uint64_t bytecodeOffsets,
|
||||
MethodDescriptor* methodDescriptor);
|
||||
|
||||
CheckTypesResult* checkExprTypeBounds();
|
||||
std::vector<Exprent*> getAllExprents() const override;
|
||||
Exprent* copy() const override;
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
void replaceExprent(Exprent* oldExpr, Exprent* newExpr) override;
|
||||
bool equals(const Exprent* o) const;
|
||||
void fillBytecodeRange(uint64_t* values) const override;
|
||||
|
||||
bool match(MatchNode* matchNode, MatchEngine* engine) override;
|
||||
|
||||
int getExitType() const { return exitType_; }
|
||||
Exprent* getValue() const { return value_; }
|
||||
VarType getRetType() const { return retType_; }
|
||||
MethodDescriptor* getMethodDescriptor() const { return methodDescriptor_; }
|
||||
|
||||
private:
|
||||
int exitType_;
|
||||
Exprent* value_;
|
||||
VarType retType_;
|
||||
MethodDescriptor* methodDescriptor_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "ExprProcessor.h"
|
||||
#include "Exprent.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "CodeConstants.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "ImportCollector.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace ff {
|
||||
|
||||
std::string ExprProcessor::getCastTypeName(const VarType& type,
|
||||
const std::vector<std::string>& excludedArrays) {
|
||||
std::string buf;
|
||||
int t = type.getType();
|
||||
int dim = type.getArrayDim();
|
||||
|
||||
if (t == CodeConstants::TYPE_OBJECT) {
|
||||
std::string val = type.getValue(); // JVM notation (slashes)
|
||||
ImportCollector* imp = DecompilerContext::getImportCollector();
|
||||
if (imp) {
|
||||
buf = imp->getShortName(val);
|
||||
} else {
|
||||
std::replace(val.begin(), val.end(), '/', '.');
|
||||
buf = val;
|
||||
}
|
||||
} else if (t == CodeConstants::TYPE_BYTE) buf = "byte";
|
||||
else if (t == CodeConstants::TYPE_CHAR) buf = "char";
|
||||
else if (t == CodeConstants::TYPE_DOUBLE) buf = "double";
|
||||
else if (t == CodeConstants::TYPE_FLOAT) buf = "float";
|
||||
else if (t == CodeConstants::TYPE_INT) buf = "int";
|
||||
else if (t == CodeConstants::TYPE_LONG) buf = "long";
|
||||
else if (t == CodeConstants::TYPE_SHORT) buf = "short";
|
||||
else if (t == CodeConstants::TYPE_BOOLEAN) buf = "boolean";
|
||||
else if (t == CodeConstants::TYPE_VOID) buf = "void";
|
||||
else buf = "Object";
|
||||
|
||||
for (int i = 0; i < dim; ++i) buf += "[]";
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string ExprProcessor::getCastTypeName(const VarType& type) {
|
||||
return getCastTypeName(type, {});
|
||||
}
|
||||
|
||||
bool ExprProcessor::getCastedExprent(Exprent* exprent, const VarType& leftType,
|
||||
TextBuffer& buffer, int indent,
|
||||
bool castAlways, BytecodeMappingTracer* tracer) {
|
||||
TextBuffer* inner = exprent->toJava(indent, tracer);
|
||||
buffer.append(inner->toString());
|
||||
delete inner;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExprProcessor::getCastedExprent(Exprent* exprent, const VarType& leftType,
|
||||
TextBuffer& buffer, int indent,
|
||||
bool castAlways, bool castNull, bool unbox,
|
||||
bool wildcards, BytecodeMappingTracer* tracer) {
|
||||
return getCastedExprent(exprent, leftType, buffer, indent, castAlways, tracer);
|
||||
}
|
||||
|
||||
std::string ExprProcessor::buildJavaClassName(const std::string& name) {
|
||||
std::string result = name;
|
||||
std::replace(result.begin(), result.end(), '/', '.');
|
||||
std::replace(result.begin(), result.end(), '$', '.');
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Exprent*> ExprProcessor::listExprent(Exprent* e) {
|
||||
if (!e) return {};
|
||||
std::vector<Exprent*> lst = e->getAllExprents(true);
|
||||
lst.push_back(e);
|
||||
return lst;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "VarType.h"
|
||||
#include "TextBuffer.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class Exprent;
|
||||
class BytecodeMappingTracer;
|
||||
|
||||
class ExprProcessor {
|
||||
public:
|
||||
static std::string getCastTypeName(const VarType& type, const std::vector<std::string>& excludedArrays);
|
||||
static std::string getCastTypeName(const VarType& type);
|
||||
|
||||
static bool getCastedExprent(Exprent* exprent, const VarType& leftType,
|
||||
TextBuffer& buffer, int indent,
|
||||
bool castAlways, BytecodeMappingTracer* tracer);
|
||||
|
||||
static bool getCastedExprent(Exprent* exprent, const VarType& leftType,
|
||||
TextBuffer& buffer, int indent,
|
||||
bool castAlways, bool castNull, bool unbox,
|
||||
bool wildcards, BytecodeMappingTracer* tracer);
|
||||
|
||||
static std::string buildJavaClassName(const std::string& name);
|
||||
|
||||
static std::vector<Exprent*> listExprent(Exprent* e);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Exprent.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "CounterContainer.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "TextBuffer.h"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ff {
|
||||
|
||||
Exprent::Exprent(int t)
|
||||
: type(t)
|
||||
, id(DecompilerContext::getCounterContainer()->getCounterAndIncrement(CounterContainer::EXPRESSION_COUNTER)) {}
|
||||
|
||||
bool Exprent::containsExprent(Exprent* exprent) const {
|
||||
if (this == exprent) return true;
|
||||
auto lst = getAllExprents(false);
|
||||
for (int i = (int)lst.size() - 1; i >= 0; i--) {
|
||||
if (lst[i]->containsExprent(exprent)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Exprent*> Exprent::getAllExprents(bool recursive) const {
|
||||
std::vector<Exprent*> lst;
|
||||
auto direct = getAllExprents();
|
||||
lst.insert(lst.end(), direct.begin(), direct.end());
|
||||
|
||||
if (recursive) {
|
||||
int end = (int)lst.size();
|
||||
for (int i = end - 1; i >= 0; i--) {
|
||||
auto sub = lst[i]->getAllExprents(true);
|
||||
lst.insert(lst.end(), sub.begin(), sub.end());
|
||||
}
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
Exprent* Exprent::copy() const {
|
||||
throw std::runtime_error("Exprent::copy() not implemented");
|
||||
}
|
||||
|
||||
TextBuffer* Exprent::toJava() {
|
||||
return toJava(0, &BytecodeMappingTracer::DUMMY);
|
||||
}
|
||||
|
||||
TextBuffer* Exprent::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
throw std::runtime_error("Exprent::toJava() not implemented");
|
||||
}
|
||||
|
||||
void Exprent::measureBytecode(uint64_t* values) const {
|
||||
if (bytecode && values) *values |= bytecode;
|
||||
}
|
||||
|
||||
void Exprent::measureBytecode(uint64_t* values, Exprent* exprent) {
|
||||
if (exprent) exprent->fillBytecodeRange(values);
|
||||
}
|
||||
|
||||
void Exprent::measureBytecode(uint64_t* values, const std::vector<Exprent*>& list) {
|
||||
for (Exprent* e : list) {
|
||||
if (e) e->fillBytecodeRange(values);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "IMatchable.h"
|
||||
#include "VarType.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class BytecodeMappingTracer;
|
||||
class TextBuffer;
|
||||
|
||||
class Exprent : public IMatchable {
|
||||
public:
|
||||
static constexpr int MULTIPLE_USES = 1;
|
||||
static constexpr int SIDE_EFFECTS_FREE = 2;
|
||||
static constexpr int BOTH_FLAGS = 3;
|
||||
|
||||
static constexpr int EXPRENT_ARRAY = 1;
|
||||
static constexpr int EXPRENT_ASSIGNMENT = 2;
|
||||
static constexpr int EXPRENT_CONST = 3;
|
||||
static constexpr int EXPRENT_EXIT = 4;
|
||||
static constexpr int EXPRENT_FIELD = 5;
|
||||
static constexpr int EXPRENT_FUNCTION = 6;
|
||||
static constexpr int EXPRENT_IF = 7;
|
||||
static constexpr int EXPRENT_INVOCATION = 8;
|
||||
static constexpr int EXPRENT_MONITOR = 9;
|
||||
static constexpr int EXPRENT_NEW = 10;
|
||||
static constexpr int EXPRENT_SWITCH = 11;
|
||||
static constexpr int EXPRENT_VAR = 12;
|
||||
static constexpr int EXPRENT_ANNOTATION = 13;
|
||||
static constexpr int EXPRENT_ASSERT = 14;
|
||||
|
||||
const int type;
|
||||
const int id;
|
||||
|
||||
// Bitset of bytecode offsets mapped to this exprent
|
||||
uint64_t bytecode = 0;
|
||||
|
||||
explicit Exprent(int type);
|
||||
~Exprent() override = default;
|
||||
|
||||
virtual int getPrecedence() const { return 0; }
|
||||
virtual VarType getExprType() const { return VarType::VARTYPE_VOID; }
|
||||
virtual void inferExprType(const VarType& upperBound) {}
|
||||
virtual int getExprentUse() const { return 0; }
|
||||
|
||||
bool containsExprent(Exprent* exprent) const;
|
||||
|
||||
virtual bool equals(const Exprent* other) const { return this == other; }
|
||||
|
||||
std::vector<Exprent*> getAllExprents(bool recursive) const;
|
||||
virtual std::vector<Exprent*> getAllExprents() const = 0;
|
||||
|
||||
virtual Exprent* copy() const;
|
||||
virtual TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer);
|
||||
TextBuffer* toJava();
|
||||
|
||||
virtual void replaceExprent(Exprent* oldExpr, Exprent* newExpr) {}
|
||||
virtual void fillBytecodeRange(uint64_t* values) const = 0;
|
||||
|
||||
// IMatchable
|
||||
IMatchable* findObject(MatchNode* matchNode, int index) override { return nullptr; }
|
||||
bool match(MatchNode* matchNode, MatchEngine* engine) override { return false; }
|
||||
|
||||
protected:
|
||||
void measureBytecode(uint64_t* values) const;
|
||||
static void measureBytecode(uint64_t* values, Exprent* exprent);
|
||||
static void measureBytecode(uint64_t* values, const std::vector<Exprent*>& list);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "VBStyleCollection.h"
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ff {
|
||||
|
||||
template<typename E>
|
||||
class FastSparseSetFactory {
|
||||
public:
|
||||
class FastSparseSet;
|
||||
|
||||
explicit FastSparseSetFactory(const std::vector<E>& elements) {
|
||||
int block = -1;
|
||||
int mask = -1;
|
||||
int index = 0;
|
||||
for (const E& elem : elements) {
|
||||
block = index / 32;
|
||||
mask = (index % 32 == 0) ? 1 : (mask << 1);
|
||||
colValuesInternal_.addWithKey(std::vector<int>{block, mask}, elem);
|
||||
index++;
|
||||
}
|
||||
lastBlock_ = block;
|
||||
lastMask_ = mask;
|
||||
}
|
||||
|
||||
FastSparseSet spawnEmptySet() { return FastSparseSet(this); }
|
||||
int getLastBlock() const { return lastBlock_; }
|
||||
|
||||
std::vector<int>* addElement(const E& element) {
|
||||
if (lastMask_ == -1 || lastMask_ == static_cast<int>(0x80000000)) {
|
||||
lastMask_ = 1;
|
||||
lastBlock_++;
|
||||
} else {
|
||||
lastMask_ <<= 1;
|
||||
}
|
||||
std::vector<int> ptr{lastBlock_, lastMask_};
|
||||
colValuesInternal_.addWithKey(ptr, element);
|
||||
return colValuesInternal_.getWithKey(element);
|
||||
}
|
||||
|
||||
VBStyleCollection<std::vector<int>, E>& getInternalValuesCollection() {
|
||||
return colValuesInternal_;
|
||||
}
|
||||
|
||||
class FastSparseSet {
|
||||
public:
|
||||
FastSparseSet() = default;
|
||||
|
||||
explicit FastSparseSet(FastSparseSetFactory<E>* factory)
|
||||
: factory_(factory)
|
||||
, colValues_(&factory->getInternalValuesCollection()) {
|
||||
int len = factory->getLastBlock() + 1;
|
||||
data_.assign(len, 0);
|
||||
next_.assign(len, 0);
|
||||
}
|
||||
|
||||
FastSparseSet getCopy() const {
|
||||
FastSparseSet copy;
|
||||
copy.factory_ = factory_;
|
||||
copy.colValues_ = colValues_;
|
||||
copy.data_ = data_;
|
||||
copy.next_ = next_;
|
||||
return copy;
|
||||
}
|
||||
|
||||
void add(const E& element) {
|
||||
auto* index = colValues_->getWithKey(element);
|
||||
if (!index) index = factory_->addElement(element);
|
||||
int block = (*index)[0];
|
||||
ensureCapacity(block);
|
||||
bool wasZero = (data_[block] == 0);
|
||||
data_[block] |= (*index)[1];
|
||||
if (wasZero) changeNext(next_, block, next_[block], block);
|
||||
}
|
||||
|
||||
void remove(const E& element) {
|
||||
auto* index = colValues_->getWithKey(element);
|
||||
if (!index) return;
|
||||
int block = (*index)[0];
|
||||
if (block < static_cast<int>(data_.size())) {
|
||||
data_[block] &= ~(*index)[1];
|
||||
if (data_[block] == 0) changeNext(next_, block, block, next_[block]);
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(const E& element) const {
|
||||
auto* index = colValues_->getWithKey(element);
|
||||
if (!index) return false;
|
||||
int block = (*index)[0];
|
||||
return block < static_cast<int>(data_.size()) && ((data_[block] & (*index)[1]) != 0);
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return data_.empty() || (next_[0] == 0 && data_[0] == 0);
|
||||
}
|
||||
|
||||
void union_(const FastSparseSet& other) {
|
||||
int pointer = 0;
|
||||
do {
|
||||
if (pointer >= static_cast<int>(data_.size())) ensureCapacity(static_cast<int>(other.data_.size()) - 1);
|
||||
bool wasZero = (data_[pointer] == 0);
|
||||
data_[pointer] |= other.data_[pointer];
|
||||
if (wasZero && data_[pointer] != 0) changeNext(next_, pointer, next_[pointer], pointer);
|
||||
pointer = other.next_[pointer];
|
||||
} while (pointer != 0);
|
||||
}
|
||||
|
||||
void intersection(const FastSparseSet& other) {
|
||||
int minLen = std::min(static_cast<int>(other.data_.size()), static_cast<int>(data_.size()));
|
||||
for (int i = minLen - 1; i >= 0; i--) data_[i] &= other.data_[i];
|
||||
for (int i = static_cast<int>(data_.size()) - 1; i >= minLen; i--) data_[i] = 0;
|
||||
setNext();
|
||||
}
|
||||
|
||||
void complement(const FastSparseSet& other) {
|
||||
int pointer = 0;
|
||||
do {
|
||||
if (pointer >= static_cast<int>(other.data_.size())) break;
|
||||
data_[pointer] &= ~other.data_[pointer];
|
||||
if (data_[pointer] == 0) changeNext(next_, pointer, pointer, next_[pointer]);
|
||||
pointer = next_[pointer];
|
||||
} while (pointer != 0);
|
||||
}
|
||||
|
||||
std::unordered_set<E> toPlainSet() const {
|
||||
std::unordered_set<E> set;
|
||||
int sz = std::min(static_cast<int>(data_.size()) * 32, colValues_->size());
|
||||
for (int i = sz - 1; i >= 0; i--) {
|
||||
const auto& index = colValues_->get(i);
|
||||
if ((data_[index[0]] & index[1]) != 0) {
|
||||
set.insert(colValues_->getKey(i));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
FastSparseSetFactory<E>* getFactory() const { return factory_; }
|
||||
const std::vector<int>& getData() const { return data_; }
|
||||
const std::vector<int>& getNext() const { return next_; }
|
||||
|
||||
private:
|
||||
FastSparseSetFactory<E>* factory_ = nullptr;
|
||||
VBStyleCollection<std::vector<int>,E>* colValues_ = nullptr;
|
||||
std::vector<int> data_;
|
||||
std::vector<int> next_;
|
||||
|
||||
void ensureCapacity(int index) {
|
||||
int newLen = static_cast<int>(data_.size());
|
||||
if (newLen == 0) newLen = 1;
|
||||
while (newLen <= index) newLen *= 2;
|
||||
data_.resize(newLen, 0);
|
||||
next_.resize(newLen, 0);
|
||||
}
|
||||
|
||||
void setNext() {
|
||||
int link = 0;
|
||||
for (int i = static_cast<int>(data_.size()) - 1; i >= 0; i--) {
|
||||
next_[i] = link;
|
||||
if (data_[i] != 0) link = i;
|
||||
}
|
||||
}
|
||||
|
||||
static void changeNext(std::vector<int>& arrnext, int key, int oldnext, int newnext) {
|
||||
for (int i = key - 1; i >= 0; i--) {
|
||||
if (arrnext[i] == oldnext) arrnext[i] = newnext;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
VBStyleCollection<std::vector<int>, E> colValuesInternal_;
|
||||
int lastBlock_ = -1;
|
||||
int lastMask_ = -1;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,230 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Fernflower.h"
|
||||
#include "CancellationManager.h"
|
||||
#include "ClassesProcessor.h"
|
||||
#include "ClasspathScanner.h"
|
||||
#include "DecompilerContext.h"
|
||||
#include "IFernflowerPreferences.h"
|
||||
#include "IdentifierConverter.h"
|
||||
#include "JADNameProvider.h"
|
||||
#include "LazyLoader.h"
|
||||
#include "MemberConverterHelper.h"
|
||||
#include "PoolInterceptor.h"
|
||||
#include "StructClass.h"
|
||||
#include "StructContext.h"
|
||||
#include "TextBuffer.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// File-local helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
std::string toUpperCase(std::string s) {
|
||||
for (char& c : s) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
||||
return s;
|
||||
}
|
||||
|
||||
bool parseSeverity(const std::string& level, IFernflowerLogger::Severity& out) {
|
||||
if (level == "TRACE") { out = IFernflowerLogger::Severity::TRACE; return true; }
|
||||
if (level == "INFO") { out = IFernflowerLogger::Severity::INFO; return true; }
|
||||
if (level == "WARN") { out = IFernflowerLogger::Severity::WARN; return true; }
|
||||
if (level == "ERROR") { out = IFernflowerLogger::Severity::ERROR; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default naming factory: returns null providers (no renaming).
|
||||
class IdentityRenamerFactory : public IVariableNamingFactory {
|
||||
public:
|
||||
std::shared_ptr<IVariableNameProvider>
|
||||
createFactory(std::shared_ptr<StructMethod>) override { return nullptr; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constructor / destructor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Fernflower::Fernflower(IBytecodeProvider* provider,
|
||||
IResultSaver* saver,
|
||||
const std::unordered_map<std::string, std::string>* customProperties,
|
||||
IFernflowerLogger* logger,
|
||||
CancellationManager* cancellationManager) {
|
||||
Props properties = IFernflowerPreferences::DEFAULTS;
|
||||
if (customProperties) {
|
||||
for (const auto& [k, v] : *customProperties) properties[k] = v;
|
||||
}
|
||||
|
||||
// Apply log level from properties before anything else logs.
|
||||
auto levIt = properties.find(IFernflowerPreferences::LOG_LEVEL);
|
||||
if (levIt != properties.end()) {
|
||||
IFernflowerLogger::Severity sev{};
|
||||
if (parseSeverity(toUpperCase(levIt->second), sev)) logger->setSeverity(sev);
|
||||
}
|
||||
|
||||
initStructures(provider, saver);
|
||||
initRenaming(properties, logger);
|
||||
|
||||
auto factory = chooseNamingFactory(properties, logger);
|
||||
installDecompilerContext(properties, logger, cancellationManager, std::move(factory));
|
||||
}
|
||||
|
||||
Fernflower::~Fernflower() = default;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase 1: build StructContext + ClassesProcessor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Fernflower::initStructures(IBytecodeProvider* provider, IResultSaver* saver) {
|
||||
structContext_ = std::make_unique<StructContext>(saver, this, std::make_unique<LazyLoader>(provider));
|
||||
classProcessor_ = std::make_unique<ClassesProcessor>(structContext_.get());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase 2: renaming infrastructure (PoolInterceptor + IdentifierConverter)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Fernflower::initRenaming(const Props& props, IFernflowerLogger* logger) {
|
||||
auto renIt = props.find(IFernflowerPreferences::RENAME_ENTITIES);
|
||||
if (renIt == props.end() || renIt->second != "1") return;
|
||||
|
||||
std::string helperClass;
|
||||
auto urcIt = props.find(IFernflowerPreferences::USER_RENAMER_CLASS);
|
||||
if (urcIt != props.end()) helperClass = urcIt->second;
|
||||
|
||||
helper_ = loadHelper(helperClass, logger);
|
||||
interceptor_ = std::make_unique<PoolInterceptor>();
|
||||
converter_ = std::make_unique<IdentifierConverter>(
|
||||
structContext_.get(), helper_.get(), interceptor_.get());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase 3: choose variable naming factory
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::shared_ptr<IVariableNamingFactory>
|
||||
Fernflower::chooseNamingFactory(const Props& props, IFernflowerLogger* /*logger*/) {
|
||||
// Dynamic factory loading via RENAMER_FACTORY key is not supported in C++.
|
||||
// Fall through to the JAD or identity factory selection.
|
||||
|
||||
auto jadIt = props.find(IFernflowerPreferences::USE_JAD_VARNAMING);
|
||||
if (jadIt != props.end() && jadIt->second == "1") {
|
||||
auto parIt = props.find(IFernflowerPreferences::USE_JAD_PARAMETER_RENAMING);
|
||||
bool renameParams = (parIt != props.end() && parIt->second == "1");
|
||||
return std::make_shared<JADNameProvider::JADNameProviderFactory>(renameParams);
|
||||
}
|
||||
|
||||
return std::make_shared<IdentityRenamerFactory>();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase 4: create and install DecompilerContext
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Fernflower::installDecompilerContext(const Props& props,
|
||||
IFernflowerLogger* logger,
|
||||
CancellationManager* cm,
|
||||
std::shared_ptr<IVariableNamingFactory> factory) {
|
||||
auto* ctx = new DecompilerContext(props, logger,
|
||||
structContext_.get(),
|
||||
classProcessor_.get(),
|
||||
interceptor_.get(),
|
||||
cm,
|
||||
std::move(factory));
|
||||
DecompilerContext::setCurrentContext(ctx);
|
||||
|
||||
if (DecompilerContext::getOption(IFernflowerPreferences::INCLUDE_ENTIRE_CLASSPATH)) {
|
||||
ClasspathScanner::addAllClasspath(*structContext_);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Fernflower::addSource(const std::string& sourcePath) {
|
||||
structContext_->addSpace(sourcePath, true);
|
||||
}
|
||||
|
||||
void Fernflower::addLibrary(const std::string& libraryPath) {
|
||||
structContext_->addSpace(libraryPath, false);
|
||||
}
|
||||
|
||||
void Fernflower::decompileContext() {
|
||||
if (converter_) converter_->rename();
|
||||
classProcessor_->loadClasses(helper_.get());
|
||||
structContext_->saveContext();
|
||||
}
|
||||
|
||||
void Fernflower::addToMustBeDecompiled(const std::string& prefix) {
|
||||
classProcessor_->addToMustBeDecompiled(prefix);
|
||||
}
|
||||
|
||||
void Fernflower::clearContext() {
|
||||
DecompilerContext::setCurrentContext(nullptr);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IDecompiledData implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::string Fernflower::getClassEntryName(const StructClass& cl, const std::string& entryName) {
|
||||
ClassesProcessor::ClassNode* node = classProcessor_->getMapRootClass(cl.qualifiedName);
|
||||
if (!node || node->type != ClassesProcessor::ClassNode::CLASS_ROOT) return {};
|
||||
|
||||
if (converter_) {
|
||||
// Converter may have renamed the class; use the simple name from qualifiedName.
|
||||
std::string simpleClassName = cl.qualifiedName.substr(cl.qualifiedName.rfind('/') + 1);
|
||||
auto dirEnd = entryName.rfind('/');
|
||||
std::string dir = (dirEnd != std::string::npos) ? entryName.substr(0, dirEnd + 1) : "";
|
||||
return dir + simpleClassName + ".java";
|
||||
}
|
||||
|
||||
auto pos = entryName.rfind(".class");
|
||||
if (pos != std::string::npos) return entryName.substr(0, pos) + ".java";
|
||||
return entryName;
|
||||
}
|
||||
|
||||
std::string Fernflower::getClassContent(const StructClass& cl) {
|
||||
ClassesProcessor::ClassNode* node = classProcessor_->getMapRootClass(cl.qualifiedName);
|
||||
if (!node) return {};
|
||||
|
||||
try {
|
||||
TextBuffer buffer(ClassesProcessor::AVERAGE_CLASS_SIZE);
|
||||
buffer.append(DecompilerContext::getProperty(IFernflowerPreferences::BANNER));
|
||||
classProcessor_->writeClass(node, buffer);
|
||||
return buffer.toString();
|
||||
} catch (const std::exception& e) {
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
"Class " + cl.qualifiedName + " couldn't be fully decompiled: " + e.what(),
|
||||
IFernflowerLogger::Severity::ERROR);
|
||||
return {};
|
||||
} catch (...) {
|
||||
DecompilerContext::getLogger()->writeMessage(
|
||||
"Class " + cl.qualifiedName + " couldn't be fully decompiled (unknown exception).",
|
||||
IFernflowerLogger::Severity::ERROR);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: load renamer or fall back to MemberConverterHelper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::shared_ptr<IMemberIdentifierRenamer> Fernflower::loadHelper(
|
||||
const std::string& className, IFernflowerLogger* logger) {
|
||||
if (!className.empty()) {
|
||||
logger->writeMessage(
|
||||
"Dynamic renamer loading is not supported in C++ port: " + className,
|
||||
IFernflowerLogger::Severity::WARN);
|
||||
}
|
||||
return std::make_shared<MemberConverterHelper>();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "IDecompiledData.h"
|
||||
#include "IBytecodeProvider.h"
|
||||
#include "IResultSaver.h"
|
||||
#include "IFernflowerLogger.h"
|
||||
#include "IMemberIdentifierRenamer.h"
|
||||
#include "IVariableNamingFactory.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// Forward declarations for types not yet translated.
|
||||
class CancellationManager;
|
||||
class StructContext;
|
||||
class StructClass;
|
||||
class ClassesProcessor;
|
||||
class IdentifierConverter;
|
||||
class PoolInterceptor;
|
||||
class LazyLoader;
|
||||
class DecompilerContext;
|
||||
class ClasspathScanner;
|
||||
|
||||
class Fernflower : public IDecompiledData {
|
||||
public:
|
||||
Fernflower(IBytecodeProvider* provider,
|
||||
IResultSaver* saver,
|
||||
const std::unordered_map<std::string, std::string>* customProperties,
|
||||
IFernflowerLogger* logger,
|
||||
CancellationManager* cancellationManager = nullptr);
|
||||
|
||||
~Fernflower() override;
|
||||
|
||||
void addSource(const std::string& sourcePath);
|
||||
void addLibrary(const std::string& libraryPath);
|
||||
void decompileContext();
|
||||
void addToMustBeDecompiled(const std::string& prefix);
|
||||
void clearContext();
|
||||
|
||||
// IDecompiledData
|
||||
std::string getClassEntryName(const StructClass& cl, const std::string& entryName) override;
|
||||
std::string getClassContent(const StructClass& cl) override;
|
||||
|
||||
private:
|
||||
using Props = std::unordered_map<std::string, std::string>;
|
||||
|
||||
static std::shared_ptr<IMemberIdentifierRenamer> loadHelper(
|
||||
const std::string& className, IFernflowerLogger* logger);
|
||||
|
||||
void initStructures(IBytecodeProvider* provider, IResultSaver* saver);
|
||||
void initRenaming(const Props& props, IFernflowerLogger* logger);
|
||||
static std::shared_ptr<IVariableNamingFactory>
|
||||
chooseNamingFactory(const Props& props, IFernflowerLogger* logger);
|
||||
void installDecompilerContext(const Props& props, IFernflowerLogger* logger,
|
||||
CancellationManager* cm,
|
||||
std::shared_ptr<IVariableNamingFactory> factory);
|
||||
|
||||
std::unique_ptr<StructContext> structContext_;
|
||||
std::unique_ptr<ClassesProcessor> classProcessor_;
|
||||
std::shared_ptr<IMemberIdentifierRenamer> helper_;
|
||||
std::unique_ptr<IdentifierConverter> converter_;
|
||||
std::unique_ptr<PoolInterceptor> interceptor_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "FieldDescriptor.h"
|
||||
#include "CodeConstants.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ============================================================================
|
||||
// Static well-known instances
|
||||
// ============================================================================
|
||||
const FieldDescriptor FieldDescriptor::SHORT_DESCRIPTOR = FieldDescriptor::parseDescriptor("Ljava/lang/Short;");
|
||||
const FieldDescriptor FieldDescriptor::INTEGER_DESCRIPTOR = FieldDescriptor::parseDescriptor("Ljava/lang/Integer;");
|
||||
const FieldDescriptor FieldDescriptor::LONG_DESCRIPTOR = FieldDescriptor::parseDescriptor("Ljava/lang/Long;");
|
||||
const FieldDescriptor FieldDescriptor::FLOAT_DESCRIPTOR = FieldDescriptor::parseDescriptor("Ljava/lang/Float;");
|
||||
const FieldDescriptor FieldDescriptor::DOUBLE_DESCRIPTOR = FieldDescriptor::parseDescriptor("Ljava/lang/Double;");
|
||||
|
||||
// ============================================================================
|
||||
// Private constructor
|
||||
// ============================================================================
|
||||
FieldDescriptor::FieldDescriptor(const std::string& descriptor)
|
||||
: type(descriptor)
|
||||
, descriptorString(descriptor)
|
||||
{}
|
||||
|
||||
// ============================================================================
|
||||
// Factory
|
||||
// ============================================================================
|
||||
FieldDescriptor FieldDescriptor::parseDescriptor(const std::string& descriptor) {
|
||||
return FieldDescriptor(descriptor);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// buildNewDescriptor
|
||||
// ============================================================================
|
||||
std::optional<std::string> FieldDescriptor::buildNewDescriptor(NewClassNameBuilder& builder) const {
|
||||
if (type.getType() == CodeConstants::TYPE_OBJECT) {
|
||||
auto newClassName = builder.buildNewClassname(type.getValue());
|
||||
if (newClassName) {
|
||||
VarType newType(type.getType(), type.getArrayDim(), *newClassName);
|
||||
return newType.toString();
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Equality / hashing
|
||||
// ============================================================================
|
||||
bool FieldDescriptor::operator==(const FieldDescriptor& o) const {
|
||||
return type == o.type;
|
||||
}
|
||||
|
||||
size_t FieldDescriptor::hashCode() const {
|
||||
return type.hashCode();
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include "VarType.h"
|
||||
#include "NewClassNameBuilder.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
class FieldDescriptor {
|
||||
public:
|
||||
// Well-known static descriptors
|
||||
static const FieldDescriptor SHORT_DESCRIPTOR;
|
||||
static const FieldDescriptor INTEGER_DESCRIPTOR;
|
||||
static const FieldDescriptor LONG_DESCRIPTOR;
|
||||
static const FieldDescriptor FLOAT_DESCRIPTOR;
|
||||
static const FieldDescriptor DOUBLE_DESCRIPTOR;
|
||||
|
||||
VarType type;
|
||||
std::string descriptorString;
|
||||
|
||||
static FieldDescriptor parseDescriptor(const std::string& descriptor);
|
||||
|
||||
// Returns a new descriptor string if a class name was remapped, else nullopt.
|
||||
std::optional<std::string> buildNewDescriptor(NewClassNameBuilder& builder) const;
|
||||
|
||||
bool operator==(const FieldDescriptor& o) const;
|
||||
bool operator!=(const FieldDescriptor& o) const { return !(*this == o); }
|
||||
size_t hashCode() const;
|
||||
|
||||
private:
|
||||
explicit FieldDescriptor(const std::string& descriptor);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "FieldExprent.h"
|
||||
#include "TextBuffer.h"
|
||||
#include "BytecodeMappingTracer.h"
|
||||
#include "MatchNode.h"
|
||||
#include "MatchEngine.h"
|
||||
#include "IMatchable.h"
|
||||
#include <variant>
|
||||
|
||||
namespace ff {
|
||||
|
||||
FieldExprent::FieldExprent(const std::string& name, const std::string& classname,
|
||||
bool isStatic, Exprent* instance, FieldDescriptor descriptor,
|
||||
uint64_t bytecodeOffsets)
|
||||
: Exprent(EXPRENT_FIELD)
|
||||
, name_(name)
|
||||
, classname_(classname)
|
||||
, isStatic_(isStatic)
|
||||
, instance_(instance)
|
||||
, descriptor_(std::move(descriptor))
|
||||
{
|
||||
bytecode = bytecodeOffsets;
|
||||
}
|
||||
|
||||
VarType FieldExprent::getExprType() const {
|
||||
if (inferredType_.has_value()) return *inferredType_;
|
||||
return descriptor_.type;
|
||||
}
|
||||
|
||||
void FieldExprent::inferExprType(const VarType& upperBound) {
|
||||
// Full implementation looks up the generic field signature from StructField
|
||||
// and resolves type parameters against the instance type. Defers to descriptor type.
|
||||
(void)upperBound;
|
||||
}
|
||||
|
||||
std::vector<Exprent*> FieldExprent::getAllExprents() const {
|
||||
if (instance_) return { instance_ };
|
||||
return {};
|
||||
}
|
||||
|
||||
Exprent* FieldExprent::copy() const {
|
||||
return new FieldExprent(name_, classname_, isStatic_,
|
||||
instance_ ? instance_->copy() : nullptr,
|
||||
descriptor_, bytecode);
|
||||
}
|
||||
|
||||
TextBuffer* FieldExprent::toJava(int indent, BytecodeMappingTracer* tracer) {
|
||||
auto* buf = new TextBuffer();
|
||||
if (isStatic_) {
|
||||
buf->append(classname_);
|
||||
buf->append('.');
|
||||
} else if (instance_) {
|
||||
TextBuffer* instBuf = instance_->toJava(indent, tracer);
|
||||
buf->append(instBuf->toString());
|
||||
delete instBuf;
|
||||
buf->append('.');
|
||||
}
|
||||
buf->append(name_);
|
||||
if (tracer) tracer->addMapping(bytecode);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void FieldExprent::replaceExprent(Exprent* oldExpr, Exprent* newExpr) {
|
||||
if (oldExpr == instance_) instance_ = newExpr;
|
||||
}
|
||||
|
||||
bool FieldExprent::equals(const Exprent* o) const {
|
||||
if (o == this) return true;
|
||||
auto* fe = dynamic_cast<const FieldExprent*>(o);
|
||||
if (!fe) return false;
|
||||
return name_ == fe->name_ && classname_ == fe->classname_ &&
|
||||
isStatic_ == fe->isStatic_ && descriptor_ == fe->descriptor_;
|
||||
}
|
||||
|
||||
void FieldExprent::fillBytecodeRange(uint64_t* values) const {
|
||||
if (instance_) measureBytecode(values, instance_);
|
||||
measureBytecode(values);
|
||||
}
|
||||
|
||||
bool FieldExprent::match(MatchNode* matchNode, MatchEngine* engine) {
|
||||
auto rule = matchNode->getRuleValue(IMatchable::MatchProperties::EXPRENT_FIELD_NAME);
|
||||
if (rule.has_value()) {
|
||||
if (rule->isVariable()) {
|
||||
return engine->checkAndSetVariableValue(std::get<std::string>(rule->value), name_);
|
||||
} else {
|
||||
return name_ == std::get<std::string>(rule->value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "Exprent.h"
|
||||
#include "FieldDescriptor.h"
|
||||
#include "VarType.h"
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class LinkConstant;
|
||||
class CheckTypesResult;
|
||||
|
||||
class FieldExprent : public Exprent {
|
||||
public:
|
||||
FieldExprent(const std::string& name, const std::string& classname, bool isStatic,
|
||||
Exprent* instance, FieldDescriptor descriptor, uint64_t bytecodeOffsets);
|
||||
|
||||
VarType getExprType() const override;
|
||||
void inferExprType(const VarType& upperBound) override;
|
||||
int getExprentUse() const override { return 0; }
|
||||
|
||||
std::vector<Exprent*> getAllExprents() const override;
|
||||
Exprent* copy() const override;
|
||||
TextBuffer* toJava(int indent, BytecodeMappingTracer* tracer) override;
|
||||
void replaceExprent(Exprent* oldExpr, Exprent* newExpr) override;
|
||||
bool equals(const Exprent* o) const;
|
||||
void fillBytecodeRange(uint64_t* values) const override;
|
||||
|
||||
bool match(MatchNode* matchNode, MatchEngine* engine) override;
|
||||
|
||||
const std::string& getName() const { return name_; }
|
||||
const std::string& getClassname() const { return classname_; }
|
||||
bool isStatic() const { return isStatic_; }
|
||||
Exprent* getInstance() const { return instance_; }
|
||||
const FieldDescriptor& getDescriptor() const { return descriptor_; }
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string classname_;
|
||||
bool isStatic_;
|
||||
Exprent* instance_;
|
||||
FieldDescriptor descriptor_;
|
||||
std::optional<VarType> inferredType_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "FinallyProcessor.h"
|
||||
|
||||
namespace ff {
|
||||
|
||||
FinallyProcessor::FinallyProcessor(StructClass* cl, StructMethod* method)
|
||||
: classStruct_(cl), method_(method) {}
|
||||
|
||||
bool FinallyProcessor::iterateGraph(RootStatement* root) {
|
||||
// Full implementation detects finally blocks by traversing the DirectGraph,
|
||||
// identifies duplicated finally handler code paths, and reconstructs proper
|
||||
// finally semantics. Requires FlattenStatementsHelper, DirectGraph traversal,
|
||||
// and bytecode-level finally block detection (952-line Java implementation).
|
||||
(void)root;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
namespace ff {
|
||||
|
||||
class RootStatement;
|
||||
class StructClass;
|
||||
class StructMethod;
|
||||
|
||||
class FinallyProcessor {
|
||||
public:
|
||||
FinallyProcessor(StructClass* cl, StructMethod* method);
|
||||
bool iterateGraph(RootStatement* root);
|
||||
|
||||
private:
|
||||
StructClass* classStruct_;
|
||||
StructMethod* method_;
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,632 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include "FlattenStatementsHelper.h"
|
||||
#include "BasicBlockStatement.h"
|
||||
#include "CatchAllStatement.h"
|
||||
#include "CatchStatement.h"
|
||||
#include "DirectGraph.h"
|
||||
#include "DirectNode.h"
|
||||
#include "DoStatement.h"
|
||||
#include "Exprent.h"
|
||||
#include "IfStatement.h"
|
||||
#include "RootStatement.h"
|
||||
#include "StatEdge.h"
|
||||
#include "Statement.h"
|
||||
#include "SwitchStatement.h"
|
||||
#include "SynchronizedStatement.h"
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ff {
|
||||
|
||||
// ---- helper structs (local to this translation unit) ----
|
||||
|
||||
struct StackEntry {
|
||||
CatchAllStatement* catchstatement;
|
||||
bool state;
|
||||
StatEdge::EdgeType edgetype;
|
||||
bool isFinallyExceptionPath;
|
||||
Statement* destination;
|
||||
Statement* finallyShortRangeEntry;
|
||||
Statement* finallyLongRangeEntry;
|
||||
DirectNode* finallyShortRangeSource;
|
||||
DirectNode* finallyLongRangeSource;
|
||||
|
||||
StackEntry(CatchAllStatement* cs, bool st,
|
||||
StatEdge::EdgeType et, Statement* dest,
|
||||
Statement* fsrEntry, Statement* flrEntry,
|
||||
DirectNode* fsrSrc, DirectNode* flrSrc,
|
||||
bool isFinallyExcPath)
|
||||
: catchstatement(cs), state(st), edgetype(et),
|
||||
isFinallyExceptionPath(isFinallyExcPath),
|
||||
destination(dest),
|
||||
finallyShortRangeEntry(fsrEntry), finallyLongRangeEntry(flrEntry),
|
||||
finallyShortRangeSource(fsrSrc), finallyLongRangeSource(flrSrc) {}
|
||||
|
||||
StackEntry(CatchAllStatement* cs, bool st)
|
||||
: StackEntry(cs, st, StatEdge::EdgeType::NULL_TYPE,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, false) {}
|
||||
};
|
||||
|
||||
struct StatementStackEntry {
|
||||
Statement* statement;
|
||||
std::list<StackEntry> stackFinally;
|
||||
std::vector<std::shared_ptr<Exprent>> tailExprents; // may be empty = null
|
||||
bool hasTailExprents = false;
|
||||
int statementIndex = 0;
|
||||
int edgeIndex = 0;
|
||||
std::vector<std::shared_ptr<StatEdge>> succEdges; // empty = null in Java
|
||||
bool hasSuccEdges = false;
|
||||
|
||||
StatementStackEntry(Statement* st, const std::list<StackEntry>& sf,
|
||||
const std::vector<std::shared_ptr<Exprent>>* tail)
|
||||
: statement(st), stackFinally(sf) {
|
||||
if (tail) { tailExprents = *tail; hasTailExprents = true; }
|
||||
}
|
||||
};
|
||||
|
||||
struct EdgeRec {
|
||||
std::string sourceid;
|
||||
int statid;
|
||||
StatEdge::EdgeType edgetype;
|
||||
};
|
||||
|
||||
// Retrieve DirectNode* from graph (getWithKey returns DirectNode** for VBC<DirectNode*,string>)
|
||||
static DirectNode* getNode(DirectGraph* graph, const std::string& id) {
|
||||
DirectNode** ptr = graph->nodes.getWithKey(id);
|
||||
return ptr ? *ptr : nullptr;
|
||||
}
|
||||
|
||||
DirectGraph* FlattenStatementsHelper::buildDirectGraph(RootStatement* root) {
|
||||
root_ = root;
|
||||
graph_ = new DirectGraph();
|
||||
|
||||
// ---- local state (mirrors Java fields) ----
|
||||
std::unordered_map<int, std::vector<std::string>> mapDestinationNodes; // statId -> [directId, continueId]
|
||||
std::vector<EdgeRec> listEdges;
|
||||
// node.id -> list of [srcId, destStatId, entryStatId, monitorFlag?, continueFlag?]
|
||||
std::unordered_map<std::string, std::vector<std::vector<std::string>>> mapShortRangeFinallyPathIds;
|
||||
std::unordered_map<std::string, std::vector<std::vector<std::string>>> mapLongRangeFinallyPathIds;
|
||||
std::unordered_map<std::string, int> mapPosIfBranch;
|
||||
|
||||
// ---- saveEdge lambda ----
|
||||
auto saveEdge = [&](DirectNode* sourcenode, Statement* destination,
|
||||
StatEdge::EdgeType edgetype,
|
||||
DirectNode* finallyShortRangeSource,
|
||||
DirectNode* finallyLongRangeSource,
|
||||
Statement* finallyShortRangeEntry,
|
||||
Statement* finallyLongRangeEntry,
|
||||
bool isFinallyMonitorExceptionPath) {
|
||||
if (edgetype != StatEdge::EdgeType::FINALLY_EXIT) {
|
||||
listEdges.push_back({sourcenode->id, destination->id, edgetype});
|
||||
}
|
||||
if (finallyShortRangeSource != nullptr) {
|
||||
bool isContinueEdge = (edgetype == StatEdge::EdgeType::CONTINUE);
|
||||
mapShortRangeFinallyPathIds[sourcenode->id].push_back({
|
||||
finallyShortRangeSource->id,
|
||||
std::to_string(destination->id),
|
||||
std::to_string(finallyShortRangeEntry->id),
|
||||
isFinallyMonitorExceptionPath ? "1" : "",
|
||||
isContinueEdge ? "1" : ""
|
||||
});
|
||||
mapLongRangeFinallyPathIds[sourcenode->id].push_back({
|
||||
finallyLongRangeSource->id,
|
||||
std::to_string(destination->id),
|
||||
std::to_string(finallyLongRangeEntry->id),
|
||||
isContinueEdge ? "1" : ""
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ---- flattenStatement loop ----
|
||||
std::deque<StatementStackEntry> lstStackStatements;
|
||||
lstStackStatements.push_back(StatementStackEntry(root, {}, nullptr));
|
||||
|
||||
while (!lstStackStatements.empty()) {
|
||||
StatementStackEntry& statEntry = lstStackStatements.front();
|
||||
Statement* stat = statEntry.statement;
|
||||
int statementBreakIndex = statEntry.statementIndex;
|
||||
|
||||
std::vector<std::shared_ptr<StatEdge>> lstSuccEdges;
|
||||
DirectNode* sourcenode = nullptr;
|
||||
|
||||
if (!statEntry.hasSuccEdges) {
|
||||
switch (stat->type) {
|
||||
case Statement::StatementType::BASIC_BLOCK: {
|
||||
auto* bbstat = static_cast<BasicBlockStatement*>(stat);
|
||||
auto* node = new DirectNode(DirectNode::DirectNodeType::DIRECT, stat, bbstat);
|
||||
if (stat->getExprents() != nullptr) {
|
||||
for (auto& ep : *stat->getExprents())
|
||||
node->exprents.push_back(ep);
|
||||
}
|
||||
graph_->nodes.addWithKey(node, node->id);
|
||||
mapDestinationNodes[stat->id] = {node->id, ""};
|
||||
|
||||
lstSuccEdges = stat->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
sourcenode = node;
|
||||
|
||||
bool hasTail = statEntry.hasTailExprents && !statEntry.tailExprents.empty()
|
||||
&& statEntry.tailExprents[0] != nullptr;
|
||||
if (hasTail) {
|
||||
auto* tail = new DirectNode(DirectNode::DirectNodeType::TAIL, stat,
|
||||
std::to_string(stat->id) + "_tail");
|
||||
tail->exprents = statEntry.tailExprents;
|
||||
graph_->nodes.addWithKey(tail, tail->id);
|
||||
|
||||
mapDestinationNodes[-(stat->id)] = {tail->id, ""};
|
||||
listEdges.push_back({node->id, -(stat->id), StatEdge::EdgeType::REGULAR});
|
||||
sourcenode = tail;
|
||||
}
|
||||
|
||||
if (stat->getLastBasicType() == Statement::StatementType::IF && !lstSuccEdges.empty()) {
|
||||
mapPosIfBranch[sourcenode->id] = lstSuccEdges[0]->getDestination()->id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Statement::StatementType::CATCH_ALL:
|
||||
case Statement::StatementType::TRY_CATCH: {
|
||||
auto* firstnd = new DirectNode(DirectNode::DirectNodeType::TRY, stat,
|
||||
std::to_string(stat->id) + "_try");
|
||||
if (stat->type == Statement::StatementType::TRY_CATCH) {
|
||||
auto* catchStat = static_cast<CatchStatement*>(stat);
|
||||
if (catchStat->getTryType() == CatchStatement::CatchStatementType::RESOURCES) {
|
||||
for (Exprent* res : catchStat->getResources()) {
|
||||
// resources are raw Exprent*, wrap in shared_ptr with no-op deleter
|
||||
firstnd->exprents.push_back(std::shared_ptr<Exprent>(res, [](Exprent*){}));
|
||||
}
|
||||
}
|
||||
}
|
||||
mapDestinationNodes[stat->id] = {firstnd->id, ""};
|
||||
graph_->nodes.addWithKey(firstnd, firstnd->id);
|
||||
|
||||
std::deque<StatementStackEntry> lst;
|
||||
auto& statsCol = stat->getStats();
|
||||
for (int i = 0; i < statsCol.size(); ++i) {
|
||||
Statement* st = statsCol.get(i);
|
||||
listEdges.push_back({firstnd->id, st->id, StatEdge::EdgeType::REGULAR});
|
||||
|
||||
std::list<StackEntry> stack = statEntry.stackFinally;
|
||||
if (stat->type == Statement::StatementType::CATCH_ALL) {
|
||||
auto* cas = static_cast<CatchAllStatement*>(stat);
|
||||
if (!cas->isMonitor()) { // isFinally in Java == !isMonitor
|
||||
std::list<StackEntry> newStack = statEntry.stackFinally;
|
||||
if (st == stat->getFirst()) {
|
||||
newStack.push_back(StackEntry(cas, false));
|
||||
} else {
|
||||
newStack.push_back(StackEntry(cas, true,
|
||||
StatEdge::EdgeType::BREAK, root->getDummyExit(),
|
||||
st, st, firstnd, firstnd, true));
|
||||
}
|
||||
stack = newStack;
|
||||
}
|
||||
}
|
||||
lst.push_back(StatementStackEntry(st, stack, nullptr));
|
||||
}
|
||||
// prepend lst to lstStackStatements (after removing current entry)
|
||||
lstStackStatements.pop_front();
|
||||
for (auto it = lst.rbegin(); it != lst.rend(); ++it) {
|
||||
lstStackStatements.push_front(std::move(*it));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
case Statement::StatementType::DO: {
|
||||
if (statementBreakIndex == 0) {
|
||||
statEntry.statementIndex = 1;
|
||||
Statement* child = stat->getFirst();
|
||||
std::list<StackEntry> sfCopy = statEntry.stackFinally;
|
||||
// Re-add statEntry (already at front) and prepend child
|
||||
StatementStackEntry childEntry(child, sfCopy, nullptr);
|
||||
lstStackStatements.pop_front();
|
||||
// push statEntry back
|
||||
lstStackStatements.push_front(StatementStackEntry(stat, sfCopy, nullptr));
|
||||
lstStackStatements[0].statementIndex = 1;
|
||||
lstStackStatements[0].stackFinally = sfCopy;
|
||||
// push child first
|
||||
lstStackStatements.push_front(std::move(childEntry));
|
||||
continue;
|
||||
}
|
||||
|
||||
DirectNode** ndPtr = graph_->nodes.getWithKey(mapDestinationNodes[stat->getFirst()->id][0]);
|
||||
DirectNode* nd = ndPtr ? *ndPtr : nullptr;
|
||||
|
||||
auto* dostat = static_cast<DoStatement*>(stat);
|
||||
DoStatement::LoopType loopType = dostat->getLoopType();
|
||||
|
||||
if (loopType == DoStatement::LoopType::DO) {
|
||||
mapDestinationNodes[stat->id] = {nd ? nd->id : "", nd ? nd->id : ""};
|
||||
break;
|
||||
}
|
||||
|
||||
lstSuccEdges = stat->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
|
||||
if (loopType == DoStatement::LoopType::WHILE ||
|
||||
loopType == DoStatement::LoopType::DO_WHILE) {
|
||||
auto* node = new DirectNode(DirectNode::DirectNodeType::CONDITION, stat,
|
||||
std::to_string(stat->id) + "_cond");
|
||||
// conditionExprentList: wrap single exprent
|
||||
if (dostat->getConditionExprent()) {
|
||||
node->exprents.push_back(std::shared_ptr<Exprent>(
|
||||
dostat->getConditionExprent(), [](Exprent*){}));
|
||||
}
|
||||
graph_->nodes.addWithKey(node, node->id);
|
||||
listEdges.push_back({node->id, stat->getFirst()->id, StatEdge::EdgeType::REGULAR});
|
||||
|
||||
if (loopType == DoStatement::LoopType::WHILE) {
|
||||
mapDestinationNodes[stat->id] = {node->id, node->id};
|
||||
} else {
|
||||
mapDestinationNodes[stat->id] = {nd ? nd->id : "", node->id};
|
||||
// Add continue edge if not already present
|
||||
bool found = false;
|
||||
for (auto& e : listEdges) {
|
||||
if (e.statid == stat->id && e.edgetype == StatEdge::EdgeType::CONTINUE) {
|
||||
found = true; break;
|
||||
}
|
||||
}
|
||||
if (!found && nd) {
|
||||
listEdges.push_back({nd->id, stat->id, StatEdge::EdgeType::CONTINUE});
|
||||
}
|
||||
}
|
||||
sourcenode = node;
|
||||
} else { // FOR, FOR_EACH
|
||||
auto* nodeinit = new DirectNode(DirectNode::DirectNodeType::INIT, stat,
|
||||
std::to_string(stat->id) + "_init");
|
||||
if (dostat->getInitExprent()) {
|
||||
nodeinit->exprents.push_back(std::shared_ptr<Exprent>(
|
||||
dostat->getInitExprent(), [](Exprent*){}));
|
||||
}
|
||||
graph_->nodes.addWithKey(nodeinit, nodeinit->id);
|
||||
|
||||
auto* nodecond = new DirectNode(DirectNode::DirectNodeType::CONDITION, stat,
|
||||
std::to_string(stat->id) + "_cond");
|
||||
if (loopType != DoStatement::LoopType::FOR_EACH && dostat->getConditionExprent()) {
|
||||
nodecond->exprents.push_back(std::shared_ptr<Exprent>(
|
||||
dostat->getConditionExprent(), [](Exprent*){}));
|
||||
}
|
||||
graph_->nodes.addWithKey(nodecond, nodecond->id);
|
||||
|
||||
auto* nodeinc = new DirectNode(DirectNode::DirectNodeType::INCREMENT, stat,
|
||||
std::to_string(stat->id) + "_inc");
|
||||
if (dostat->getIncExprent()) {
|
||||
nodeinc->exprents.push_back(std::shared_ptr<Exprent>(
|
||||
dostat->getIncExprent(), [](Exprent*){}));
|
||||
}
|
||||
graph_->nodes.addWithKey(nodeinc, nodeinc->id);
|
||||
|
||||
mapDestinationNodes[stat->id] = {nodeinit->id, nodeinc->id};
|
||||
mapDestinationNodes[-(stat->id)] = {nodecond->id, ""};
|
||||
|
||||
listEdges.push_back({nodecond->id, stat->getFirst()->id, StatEdge::EdgeType::REGULAR});
|
||||
listEdges.push_back({nodeinit->id, -(stat->id), StatEdge::EdgeType::REGULAR});
|
||||
listEdges.push_back({nodeinc->id, -(stat->id), StatEdge::EdgeType::REGULAR});
|
||||
|
||||
bool found = false;
|
||||
for (auto& e : listEdges) {
|
||||
if (e.statid == stat->id && e.edgetype == StatEdge::EdgeType::CONTINUE) {
|
||||
found = true; break;
|
||||
}
|
||||
}
|
||||
if (!found && nd) {
|
||||
listEdges.push_back({nd->id, stat->id, StatEdge::EdgeType::CONTINUE});
|
||||
}
|
||||
sourcenode = nodecond;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Statement::StatementType::SYNCHRONIZED:
|
||||
case Statement::StatementType::SWITCH:
|
||||
case Statement::StatementType::IF:
|
||||
case Statement::StatementType::SEQUENCE:
|
||||
case Statement::StatementType::ROOT: {
|
||||
auto& statsCol = stat->getStats();
|
||||
int statsize = statsCol.size();
|
||||
if (stat->type == Statement::StatementType::SYNCHRONIZED) {
|
||||
statsize = 2; // exclude handler
|
||||
}
|
||||
|
||||
if (statementBreakIndex <= statsize) {
|
||||
// Gather tail exprent list for first child
|
||||
std::vector<std::shared_ptr<Exprent>>* tailexprlst = nullptr;
|
||||
std::vector<std::shared_ptr<Exprent>> singletonList;
|
||||
|
||||
if (stat->type == Statement::StatementType::SYNCHRONIZED) {
|
||||
auto* sy = static_cast<SynchronizedStatement*>(stat);
|
||||
if (sy->getHeadExprent()) {
|
||||
singletonList.push_back(std::shared_ptr<Exprent>(
|
||||
sy->getHeadExprent(), [](Exprent*){}));
|
||||
tailexprlst = &singletonList;
|
||||
}
|
||||
} else if (stat->type == Statement::StatementType::SWITCH) {
|
||||
auto* sw = static_cast<SwitchStatement*>(stat);
|
||||
if (sw->getHeadExprent()) {
|
||||
singletonList.push_back(std::shared_ptr<Exprent>(
|
||||
sw->getHeadExprent(), [](Exprent*){}));
|
||||
tailexprlst = &singletonList;
|
||||
}
|
||||
} else if (stat->type == Statement::StatementType::IF) {
|
||||
auto* ifs = static_cast<IfStatement*>(stat);
|
||||
if (ifs->getHeadExprent()) {
|
||||
singletonList.push_back(std::shared_ptr<Exprent>(
|
||||
ifs->getHeadExprent(), [](Exprent*){}));
|
||||
tailexprlst = &singletonList;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = statementBreakIndex; i < statsize; ++i) {
|
||||
statEntry.statementIndex = i + 1;
|
||||
Statement* child = statsCol.get(i);
|
||||
std::list<StackEntry> sfCopy = statEntry.stackFinally;
|
||||
bool isFirst = (i == 0);
|
||||
bool hasTail = isFirst && tailexprlst != nullptr &&
|
||||
!tailexprlst->empty() && (*tailexprlst)[0] != nullptr;
|
||||
|
||||
StatementStackEntry childEntry(child, sfCopy,
|
||||
hasTail ? tailexprlst : nullptr);
|
||||
// pop current and re-push state, then push child
|
||||
lstStackStatements.pop_front();
|
||||
lstStackStatements.push_front(StatementStackEntry(stat, sfCopy, nullptr));
|
||||
lstStackStatements[0].statementIndex = i + 1;
|
||||
lstStackStatements[0].stackFinally = sfCopy;
|
||||
lstStackStatements[0].hasTailExprents = false;
|
||||
lstStackStatements.push_front(std::move(childEntry));
|
||||
goto continue_mainloop;
|
||||
}
|
||||
|
||||
// All children processed
|
||||
{
|
||||
DirectNode** nodePtr = graph_->nodes.getWithKey(
|
||||
mapDestinationNodes[stat->getFirst()->id][0]);
|
||||
DirectNode* fnode = nodePtr ? *nodePtr : nullptr;
|
||||
if (fnode) {
|
||||
mapDestinationNodes[stat->id] = {fnode->id, ""};
|
||||
}
|
||||
|
||||
if (stat->type == Statement::StatementType::IF) {
|
||||
auto* ifs = static_cast<IfStatement*>(stat);
|
||||
if (ifs->iftype == IfStatement::IFTYPE_IF) {
|
||||
auto succEdgesAll = stat->getSuccessorEdges(StatEdge::EdgeType::DIRECT_ALL);
|
||||
if (!succEdgesAll.empty()) {
|
||||
lstSuccEdges.push_back(succEdgesAll[0]);
|
||||
}
|
||||
bool noTail = tailexprlst == nullptr || tailexprlst->empty()
|
||||
|| (*tailexprlst)[0] == nullptr;
|
||||
if (noTail && fnode) {
|
||||
sourcenode = fnode;
|
||||
} else if (fnode) {
|
||||
DirectNode** tailPtr = graph_->nodes.getWithKey(fnode->id + "_tail");
|
||||
sourcenode = tailPtr ? *tailPtr : fnode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} // end if (!hasSuccEdges)
|
||||
|
||||
// Process successor edges
|
||||
if (sourcenode != nullptr) {
|
||||
std::vector<std::shared_ptr<StatEdge>> edgesToProcess;
|
||||
if (statEntry.hasSuccEdges) {
|
||||
edgesToProcess = statEntry.succEdges;
|
||||
} else {
|
||||
edgesToProcess = lstSuccEdges;
|
||||
}
|
||||
|
||||
for (int edgeindex = statEntry.edgeIndex;
|
||||
edgeindex < (int)edgesToProcess.size(); ++edgeindex) {
|
||||
|
||||
auto& edge = edgesToProcess[edgeindex];
|
||||
std::list<StackEntry> stack = statEntry.stackFinally;
|
||||
|
||||
StatEdge::EdgeType edgetype = edge->getType();
|
||||
Statement* destination = edge->getDestination();
|
||||
|
||||
DirectNode* finallyShortRangeSource = sourcenode;
|
||||
DirectNode* finallyLongRangeSource = sourcenode;
|
||||
Statement* finallyShortRangeEntry = nullptr;
|
||||
Statement* finallyLongRangeEntry = nullptr;
|
||||
bool isFinallyMonitorExceptionPath = false;
|
||||
bool isFinallyExit = false;
|
||||
|
||||
while (true) {
|
||||
StackEntry* entry = nullptr;
|
||||
if (!stack.empty()) {
|
||||
entry = &stack.back();
|
||||
}
|
||||
bool created = true;
|
||||
|
||||
if (entry == nullptr) {
|
||||
saveEdge(sourcenode, destination, edgetype,
|
||||
isFinallyExit ? finallyShortRangeSource : nullptr,
|
||||
finallyLongRangeSource,
|
||||
finallyShortRangeEntry, finallyLongRangeEntry,
|
||||
isFinallyMonitorExceptionPath);
|
||||
} else {
|
||||
CatchAllStatement* catchall = entry->catchstatement;
|
||||
|
||||
if (entry->state) { // finally handler
|
||||
if (edgetype == StatEdge::EdgeType::FINALLY_EXIT) {
|
||||
stack.pop_back();
|
||||
destination = entry->destination;
|
||||
edgetype = entry->edgetype;
|
||||
|
||||
finallyShortRangeSource = entry->finallyShortRangeSource;
|
||||
finallyLongRangeSource = entry->finallyLongRangeSource;
|
||||
finallyShortRangeEntry = entry->finallyShortRangeEntry;
|
||||
finallyLongRangeEntry = entry->finallyLongRangeEntry;
|
||||
|
||||
isFinallyExit = true;
|
||||
isFinallyMonitorExceptionPath =
|
||||
(catchall->getMonitorVar() != nullptr) & entry->isFinallyExceptionPath;
|
||||
created = false;
|
||||
} else {
|
||||
if (!catchall->containsStatementStrict(destination)) {
|
||||
stack.pop_back();
|
||||
created = false;
|
||||
} else {
|
||||
saveEdge(sourcenode, destination, edgetype,
|
||||
isFinallyExit ? finallyShortRangeSource : nullptr,
|
||||
finallyLongRangeSource,
|
||||
finallyShortRangeEntry, finallyLongRangeEntry,
|
||||
isFinallyMonitorExceptionPath);
|
||||
}
|
||||
}
|
||||
} else { // finally protected try
|
||||
if (!catchall->containsStatementStrict(destination)) {
|
||||
saveEdge(sourcenode, catchall->getHandler(), StatEdge::EdgeType::REGULAR,
|
||||
isFinallyExit ? finallyShortRangeSource : nullptr,
|
||||
finallyLongRangeSource,
|
||||
finallyShortRangeEntry, finallyLongRangeEntry,
|
||||
isFinallyMonitorExceptionPath);
|
||||
|
||||
stack.pop_back();
|
||||
Statement* flrEntry2 = (finallyLongRangeEntry == nullptr)
|
||||
? catchall->getHandler() : finallyLongRangeEntry;
|
||||
stack.push_back(StackEntry(catchall, true, edgetype, destination,
|
||||
catchall->getHandler(), flrEntry2,
|
||||
sourcenode, finallyLongRangeSource, false));
|
||||
|
||||
statEntry.edgeIndex = edgeindex + 1;
|
||||
statEntry.hasSuccEdges = true;
|
||||
statEntry.succEdges = edgesToProcess;
|
||||
|
||||
// Pop front (statEntry) and re-add handler then statEntry
|
||||
lstStackStatements.pop_front();
|
||||
lstStackStatements.push_front(
|
||||
StatementStackEntry(stat, stack, nullptr));
|
||||
lstStackStatements[0].edgeIndex = edgeindex + 1;
|
||||
lstStackStatements[0].hasSuccEdges = true;
|
||||
lstStackStatements[0].succEdges = edgesToProcess;
|
||||
lstStackStatements[0].stackFinally = stack;
|
||||
lstStackStatements.push_front(
|
||||
StatementStackEntry(catchall->getHandler(), stack, nullptr));
|
||||
goto continue_mainloop;
|
||||
} else {
|
||||
saveEdge(sourcenode, destination, edgetype,
|
||||
isFinallyExit ? finallyShortRangeSource : nullptr,
|
||||
finallyLongRangeSource,
|
||||
finallyShortRangeEntry, finallyLongRangeEntry,
|
||||
isFinallyMonitorExceptionPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (created) break;
|
||||
}
|
||||
} // for edgeindex
|
||||
}
|
||||
|
||||
lstStackStatements.pop_front();
|
||||
continue;
|
||||
|
||||
continue_mainloop:;
|
||||
}
|
||||
|
||||
// ---- dummy exit node ----
|
||||
Statement* dummyexit = root->getDummyExit();
|
||||
auto* exitNode = new DirectNode(DirectNode::DirectNodeType::DIRECT, dummyexit,
|
||||
std::to_string(dummyexit->id));
|
||||
exitNode->exprents.clear();
|
||||
graph_->nodes.addWithKey(exitNode, exitNode->id);
|
||||
mapDestinationNodes[dummyexit->id] = {exitNode->id, ""};
|
||||
|
||||
// ---- setEdges ----
|
||||
for (auto& edge : listEdges) {
|
||||
int statid = edge.statid;
|
||||
bool isContinue = (edge.edgetype == StatEdge::EdgeType::CONTINUE);
|
||||
|
||||
auto it = mapDestinationNodes.find(statid);
|
||||
if (it == mapDestinationNodes.end()) continue;
|
||||
const std::string& destId = (isContinue && it->second.size() > 1 && !it->second[1].empty())
|
||||
? it->second[1] : it->second[0];
|
||||
if (destId.empty()) continue;
|
||||
|
||||
DirectNode** srcPtr = graph_->nodes.getWithKey(edge.sourceid);
|
||||
DirectNode** destPtr = graph_->nodes.getWithKey(destId);
|
||||
if (!srcPtr || !destPtr) continue;
|
||||
DirectNode* source = *srcPtr;
|
||||
DirectNode* dest = *destPtr;
|
||||
|
||||
if (std::find(source->successors.begin(), source->successors.end(), dest)
|
||||
== source->successors.end()) {
|
||||
source->successors.push_back(dest);
|
||||
}
|
||||
if (std::find(dest->predecessors.begin(), dest->predecessors.end(), source)
|
||||
== dest->predecessors.end()) {
|
||||
dest->predecessors.push_back(source);
|
||||
}
|
||||
|
||||
auto posIt = mapPosIfBranch.find(edge.sourceid);
|
||||
if (posIt != mapPosIfBranch.end() && statid != posIt->second) {
|
||||
graph_->mapNegIfBranch[edge.sourceid] = dest->id;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- finally path maps ----
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
auto& srcMap = (pass == 0) ? mapShortRangeFinallyPathIds : mapLongRangeFinallyPathIds;
|
||||
auto& dstMap = (pass == 0) ? graph_->mapShortRangeFinallyPaths : graph_->mapLongRangeFinallyPaths;
|
||||
|
||||
for (auto& [exitId, lst] : srcMap) {
|
||||
std::vector<FlattenStatementsHelper::FinallyPathWrapper> newLst;
|
||||
for (auto& arr : lst) {
|
||||
int flagIdx = (pass == 0) ? 4 : 3;
|
||||
bool isContinue = (arr[flagIdx] == "1");
|
||||
int destStatId = std::stoi(arr[1]);
|
||||
int entryStatId = std::stoi(arr[2]);
|
||||
|
||||
auto destIt = mapDestinationNodes.find(destStatId);
|
||||
auto entIt = mapDestinationNodes.find(entryStatId);
|
||||
if (destIt == mapDestinationNodes.end() || entIt == mapDestinationNodes.end()) continue;
|
||||
|
||||
const std::string& destNodeId = (isContinue && destIt->second.size() > 1 && !destIt->second[1].empty())
|
||||
? destIt->second[1] : destIt->second[0];
|
||||
const std::string& entryNodeId = entIt->second[0];
|
||||
|
||||
DirectNode** destPtr = graph_->nodes.getWithKey(destNodeId);
|
||||
DirectNode** entryPtr = graph_->nodes.getWithKey(entryNodeId);
|
||||
if (!destPtr || !entryPtr) continue;
|
||||
|
||||
FlattenStatementsHelper::FinallyPathWrapper wrapper(arr[0], (*destPtr)->id);
|
||||
// Check for duplicates
|
||||
bool dup = false;
|
||||
for (auto& w : newLst) {
|
||||
if (w.source == wrapper.source && w.destination == wrapper.destination) {
|
||||
dup = true; break;
|
||||
}
|
||||
}
|
||||
if (!dup) {
|
||||
newLst.push_back(wrapper);
|
||||
}
|
||||
|
||||
if (pass == 0 && arr[3] == "1") {
|
||||
graph_->mapFinallyMonitorExceptionPathExits[exitId] = (*destPtr)->id;
|
||||
}
|
||||
}
|
||||
if (!newLst.empty()) {
|
||||
dstMap[exitId] = newLst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- set graph.first and sort ----
|
||||
auto firstIt = mapDestinationNodes.find(root->id);
|
||||
if (firstIt != mapDestinationNodes.end() && !firstIt->second[0].empty()) {
|
||||
DirectNode** fp = graph_->nodes.getWithKey(firstIt->second[0]);
|
||||
if (fp) graph_->first = *fp;
|
||||
}
|
||||
graph_->sortReversePostOrder();
|
||||
|
||||
return graph_;
|
||||
}
|
||||
|
||||
} // namespace ff
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
namespace ff {
|
||||
|
||||
class DirectGraph;
|
||||
class DirectNode;
|
||||
class Exprent;
|
||||
class RootStatement;
|
||||
class Statement;
|
||||
class StatEdge;
|
||||
|
||||
class FlattenStatementsHelper {
|
||||
public:
|
||||
struct FinallyPathWrapper {
|
||||
std::string source;
|
||||
std::string destination;
|
||||
FinallyPathWrapper(std::string s, std::string d) : source(std::move(s)), destination(std::move(d)) {}
|
||||
};
|
||||
|
||||
DirectGraph* buildDirectGraph(RootStatement* root);
|
||||
|
||||
private:
|
||||
struct Edge {
|
||||
std::string sourceId;
|
||||
int destStatId;
|
||||
int edgeType;
|
||||
};
|
||||
|
||||
std::unordered_map<int, std::vector<std::string>> mapDestinationNodes_;
|
||||
std::vector<Edge> listEdges_;
|
||||
std::unordered_map<std::string, std::vector<std::vector<std::string>>> mapShortRangeFinallyPathIds_;
|
||||
std::unordered_map<std::string, std::vector<std::vector<std::string>>> mapLongRangeFinallyPathIds_;
|
||||
std::unordered_map<std::string, int> mapPosIfBranch_;
|
||||
|
||||
DirectGraph* graph_ = nullptr;
|
||||
RootStatement* root_ = nullptr;
|
||||
|
||||
void flattenStatement(Statement* stat, DirectNode* parent, int edgeType, DirectNode* ahead);
|
||||
DirectNode* createDirectNode(Statement* stat, DirectNode* parent);
|
||||
void addEdge(DirectNode* from, int toStatId, int edgeType);
|
||||
};
|
||||
|
||||
} // namespace ff
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user