This commit is contained in:
2026-05-15 23:54:29 +02:00
parent 6fe9549d52
commit 747ba38abf
300 changed files with 22442 additions and 34 deletions
+4
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -4,6 +4,7 @@
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
+1
View File
@@ -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;
}
}
+74
View File
@@ -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
+35
View File
@@ -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
+75
View File
@@ -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
+35
View File
@@ -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
+104
View File
@@ -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
+43
View File
@@ -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
+42
View File
@@ -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
+40
View File
@@ -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
+118
View File
@@ -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
+72
View File
@@ -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
+100
View File
@@ -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
+31
View File
@@ -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
+50
View File
@@ -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
+43
View File
@@ -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
+73
View File
@@ -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
+36
View File
@@ -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
+163
View File
@@ -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})
+40
View File
@@ -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
+44
View File
@@ -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
+118
View File
@@ -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
+39
View File
@@ -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
+149
View File
@@ -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
+40
View File
@@ -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
+36
View File
@@ -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
+15
View File
@@ -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
+14
View File
@@ -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
+177
View File
@@ -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
+37
View File
@@ -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
+24
View File
@@ -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
+718
View File
@@ -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
+25
View File
@@ -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
+344
View File
@@ -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
+67
View File
@@ -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
+14
View File
@@ -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
+17
View File
@@ -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
+71
View File
@@ -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
+16
View File
@@ -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
+361
View File
@@ -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
+168
View File
@@ -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
+65
View File
@@ -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
+400
View File
@@ -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
+79
View File
@@ -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
+113
View File
@@ -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
+50
View File
@@ -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
+609
View File
@@ -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 = &currentBlock->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
+88
View File
@@ -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
+25
View File
@@ -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
+186
View File
@@ -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
+71
View File
@@ -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
+48
View File
@@ -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
+32
View File
@@ -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
+220
View File
@@ -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
+19
View File
@@ -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
+90
View File
@@ -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
+17
View File
@@ -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
+97
View File
@@ -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
+85
View File
@@ -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
+99
View File
@@ -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
+41
View File
@@ -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
+14
View File
@@ -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
+40
View File
@@ -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
+72
View File
@@ -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
+51
View File
@@ -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
+103
View File
@@ -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
+17
View File
@@ -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
+12
View File
@@ -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
+20
View File
@@ -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
+17
View File
@@ -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
+22
View File
@@ -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
+58
View File
@@ -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
+37
View File
@@ -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
+9
View File
@@ -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
+22
View File
@@ -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
+95
View File
@@ -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
+42
View File
@@ -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
+77
View File
@@ -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
+33
View File
@@ -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
+66
View File
@@ -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
+76
View File
@@ -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
+182
View File
@@ -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
+230
View File
@@ -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
+69
View File
@@ -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
+55
View File
@@ -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
+34
View File
@@ -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
+93
View File
@@ -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
+48
View File
@@ -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
+19
View File
@@ -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
+20
View File
@@ -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
+632
View File
@@ -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
+49
View File
@@ -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