Add an optimizing compiler fuzzer
Bug: 438934944
Flag: EXEMPT testing only
Test: atest FuzzerCorpusTest
Test: m test-art-host-gtest-art_runtime_tests64
Test: Build/Run optimized compiler fuzzer
Test: Build/Run optimized classes verifier fuzzer
Change-Id: I6a39d3849d476a228b6837d093a5e8c664e71a2a
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index d790660..f89d9a4 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -671,6 +671,7 @@
# Fuzzer cases
self._checker.check_art_test_data('dex_verification_fuzzer_corpus.zip')
self._checker.check_art_test_data('class_verification_fuzzer_corpus.zip')
+ self._checker.check_art_test_data('optimized_compiler_fuzzer_corpus.zip')
class NoSuperfluousFilesChecker:
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index ae1ab5d..6c2a87f 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -33,6 +33,13 @@
namespace art HIDDEN {
+// Forward declare CompilerOptions so that the CreateCompilerOptions forward declare works.
+class CompilerOptions;
+
+namespace fuzzer {
+std::unique_ptr<CompilerOptions> CreateCompilerOptions();
+} // namespace fuzzer
+
namespace jit {
class JitCompiler;
} // namespace jit
@@ -414,7 +421,6 @@
// Classes listed in the preloaded-classes file, used for boot image and
// boot image extension compilation.
HashSet<std::string> preloaded_classes_;
-
CompilerType compiler_type_;
ImageType image_type_;
bool multi_image_;
@@ -505,6 +511,8 @@
friend class linker::Arm64RelativePatcherTest;
friend class linker::Thumb2RelativePatcherTest;
+ friend std::unique_ptr<CompilerOptions> fuzzer::CreateCompilerOptions();
+
template <class Base>
friend bool ReadCompilerOptions(Base& map, CompilerOptions* options, std::string* error_msg);
diff --git a/runtime/Android.bp b/runtime/Android.bp
index e0a6dc5..8f5f238 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -1064,6 +1064,7 @@
":art-gtest-jars-AllFields",
":art-gtest-jars-DexVerificationFuzzerFolder",
":art-gtest-jars-ClassVerificationFuzzerFolder",
+ ":art-gtest-jars-OptimizedCompilerFuzzerFolder",
":art-gtest-jars-ErroneousA",
":art-gtest-jars-ErroneousB",
":art-gtest-jars-ErroneousInit",
diff --git a/runtime/art_standalone_runtime_tests.xml b/runtime/art_standalone_runtime_tests.xml
index d71173a..93f7056 100644
--- a/runtime/art_standalone_runtime_tests.xml
+++ b/runtime/art_standalone_runtime_tests.xml
@@ -65,6 +65,7 @@
<option name="push" value="art-gtest-jars-XandY.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-XandY.jar" />
<option name="push" value="dex_verification_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/dex_verification_fuzzer_corpus.zip" />
<option name="push" value="class_verification_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/class_verification_fuzzer_corpus.zip" />
+ <option name="push" value="optimized_compiler_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/optimized_compiler_fuzzer_corpus.zip" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/runtime/fuzzer_corpus_test.cc b/runtime/fuzzer_corpus_test.cc
index f360901..88d9218 100644
--- a/runtime/fuzzer_corpus_test.cc
+++ b/runtime/fuzzer_corpus_test.cc
@@ -59,9 +59,35 @@
jobject class_loader = fuzzer::RegisterDexFileAndGetClassLoader(runtime, dex_file.get());
const bool passed_class_verification = fuzzer::VerifyClasses(class_loader, dex_file.get());
+ fuzzer::IterationCleanup(class_loader, dex_file.get());
ASSERT_EQ(passed_class_verification, expected_success) << " Failed for " << name;
}
+ static void OptimizedCompilation(const uint8_t* data,
+ size_t size,
+ const std::string& name,
+ bool expected_success) {
+ std::unique_ptr<StandardDexFile> dex_file = fuzzer::VerifyDexFile(data, size, name);
+ ASSERT_EQ(dex_file != nullptr, true) << " Failed for " << name;
+
+ Runtime* runtime = Runtime::Current();
+ CHECK(runtime != nullptr);
+
+ fuzzer::FuzzerCompiledMethodStorage storage;
+ std::unique_ptr<fuzzer::FuzzerCompilerCallbacks> callbacks(
+ new fuzzer::FuzzerCompilerCallbacks());
+ std::unique_ptr<CompilerOptions> compiler_options = fuzzer::CreateCompilerOptions();
+ std::unique_ptr<Compiler> compiler(fuzzer::CreateCompiler(*compiler_options, &storage));
+
+ jobject class_loader = fuzzer::RegisterDexFileAndGetClassLoader(runtime, dex_file.get());
+ fuzzer::VerifyClasses(class_loader, dex_file.get());
+ const bool at_least_one_method_called_the_compiler = fuzzer::CompileClasses(
+ class_loader, dex_file.get(), compiler.get(), callbacks.get(), /*kDebugPrints=*/false);
+ // Note: no need to reset callbacks as they will get destroyed
+ fuzzer::IterationCleanup(class_loader, dex_file.get());
+ ASSERT_EQ(at_least_one_method_called_the_compiler, expected_success) << " Failed for " << name;
+ }
+
void TestFuzzerHelper(
const std::string& archive_filename,
const std::unordered_set<std::string>& valid_dex_files,
@@ -132,4 +158,13 @@
TestFuzzerHelper(archive_filename, valid_dex_files, ClassVerification);
}
+// Tests that we can compile classes with kOptimizing from dex files without crashing.
+TEST_F(FuzzerCorpusTest, OptimizeCompileDexFiles) {
+ // These dex files are expected to pass verification. The others are regressions tests.
+ const std::unordered_set<std::string> valid_dex_files = {"Main.dex", "hello_world.dex"};
+ const std::string archive_filename = "optimized_compiler_fuzzer_corpus.zip";
+
+ TestFuzzerHelper(archive_filename, valid_dex_files, OptimizedCompilation);
+}
+
} // namespace art
diff --git a/runtime/noop_compiler_callbacks.h b/runtime/noop_compiler_callbacks.h
index 9432c53..64b9cf0 100644
--- a/runtime/noop_compiler_callbacks.h
+++ b/runtime/noop_compiler_callbacks.h
@@ -24,7 +24,7 @@
namespace art HIDDEN {
// Used for tests and some tools that pretend to be a compiler (say, oatdump).
-class NoopCompilerCallbacks final : public CompilerCallbacks {
+class NoopCompilerCallbacks : public CompilerCallbacks {
public:
NoopCompilerCallbacks() : CompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp) {}
~NoopCompilerCallbacks() {}
diff --git a/tools/fuzzer/Android.bp b/tools/fuzzer/Android.bp
index cd06f55..e73e5f8 100644
--- a/tools/fuzzer/Android.bp
+++ b/tools/fuzzer/Android.bp
@@ -200,6 +200,35 @@
],
}
+// Optimizing compiler
+cc_fuzz {
+ name: "libart_optimizing_compiler_fuzzer",
+ srcs: ["libart_optimizing_compiler_fuzzer.cc"],
+ defaults: [
+ "libart_fuzzer-defaults",
+ "fuzzer-creating-runtime-defaults",
+ ],
+ // Can not be in defaults due to soong limitations.
+ device_common_corpus: [
+ ":art_runtest_corpus",
+ "class-verifier-corpus/*",
+ ],
+}
+
+cc_fuzz {
+ name: "libart_optimizing_compiler_fuzzerd",
+ srcs: ["libart_optimizing_compiler_fuzzer.cc"],
+ defaults: [
+ "libart_fuzzerd-defaults",
+ "fuzzer-creating-runtime-defaults",
+ ],
+ // Can not be in defaults due to soong limitations.
+ device_common_corpus: [
+ ":art_runtest_corpus",
+ "class-verifier-corpus/*",
+ ],
+}
+
// Build an ART apex and extract the .jar from them. In particular, the core-oj and
// core-libart jars will have hiddenapi data, which is needed for the debug+target
// version of the fuzzer.
@@ -1457,3 +1486,14 @@
out: ["class_verification_fuzzer_corpus.zip"],
tools: ["soong_zip"],
}
+
+genrule {
+ name: "art-gtest-jars-OptimizedCompilerFuzzerFolder",
+ // Zip the optimized-compiler-corpus folder. To get the folder, we grab the first file
+ // from `in` and use its directory.
+ cmd: "FILES=($(in)) &&" +
+ "$(location soong_zip) -j -L 0 -o $(out) -D $$(dirname $${FILES[0]})",
+ srcs: ["optimized-compiler-corpus/*"],
+ out: ["optimized_compiler_fuzzer_corpus.zip"],
+ tools: ["soong_zip"],
+}
diff --git a/tools/fuzzer/fuzzer_common.cc b/tools/fuzzer/fuzzer_common.cc
index b1b75f0..2a0f136 100644
--- a/tools/fuzzer/fuzzer_common.cc
+++ b/tools/fuzzer/fuzzer_common.cc
@@ -21,7 +21,6 @@
#include "dex/class_accessor-inl.h"
#include "handle_scope-inl.h"
#include "interpreter/unstarted_runtime.h"
-#include "noop_compiler_callbacks.h"
#include "runtime_intrinsics.h"
#include "scoped_thread_state_change-inl.h"
@@ -64,16 +63,13 @@
return option + android::base::Join(class_path, ':');
}
-void FuzzerInitialize() {
+void FuzzerInitialize(CompilerCallbacks* callbacks) {
// Set logging to error and above to avoid warnings about unexpected checksums.
android::base::SetMinimumLogSeverity(android::base::ERROR);
// Create runtime.
RuntimeOptions options;
- {
- static NoopCompilerCallbacks callbacks;
- options.push_back(std::make_pair("compilercallbacks", &callbacks));
- }
+ options.push_back(std::make_pair("compilercallbacks", callbacks));
std::string boot_class_path_string =
GetClassPathOption("-Xbootclasspath:", GetLibCoreDexFileNames());
@@ -147,67 +143,68 @@
ClassLinker* class_linker = runtime->GetClassLinker();
ScopedObjectAccess soa(Thread::Current());
- // Scope for the handles
- {
- StackHandleScope<4> scope(soa.Self());
- Handle<mirror::ClassLoader> h_loader =
- scope.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
- MutableHandle<mirror::Class> h_klass(scope.NewHandle<mirror::Class>(nullptr));
- MutableHandle<mirror::DexCache> h_dex_cache(scope.NewHandle<mirror::DexCache>(nullptr));
- MutableHandle<mirror::ClassLoader> h_dex_cache_class_loader = scope.NewHandle(h_loader.Get());
+ StackHandleScope<4> scope(soa.Self());
+ Handle<mirror::ClassLoader> h_loader =
+ scope.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
+ MutableHandle<mirror::Class> h_klass(scope.NewHandle<mirror::Class>(nullptr));
+ MutableHandle<mirror::DexCache> h_dex_cache(scope.NewHandle<mirror::DexCache>(nullptr));
+ MutableHandle<mirror::ClassLoader> h_dex_cache_class_loader = scope.NewHandle(h_loader.Get());
- for (const ClassAccessor accessor : dex_file->GetClasses()) {
- h_klass.Assign(
- class_linker->FindClass(soa.Self(), *dex_file, accessor.GetClassIdx(), h_loader));
- // Ignore classes that couldn't be loaded since we are looking for crashes during
- // class/method verification.
- if (h_klass == nullptr || h_klass->IsErroneous()) {
- // Treat as failure to pass verification
- passed_class_verification = false;
- soa.Self()->ClearException();
- continue;
- }
- if (&h_klass->GetDexFile() != dex_file) {
- // Skip a duplicate class (as the resolved class is from another dex file). This can happen
- // e.g. if we have a class named "sun.misc.Unsafe" in fuzz.dex.
- continue;
- }
-
- h_dex_cache.Assign(h_klass->GetDexCache());
-
- // The class loader from the class's dex cache is different from the dex file's class loader
- // for boot image classes e.g. java.util.AbstractCollection.
- h_dex_cache_class_loader.Assign(h_klass->GetDexCache()->GetClassLoader());
-
- CHECK(h_klass->IsResolved()) << h_klass->PrettyClass();
- verifier::FailureKind failure_kind =
- class_linker->VerifyClass(soa.Self(),
- /* verifier_deps= */ nullptr,
- h_klass,
- // Don't abort on verification errors.
- verifier::HardFailLogMode::kLogWarning);
-
- DCHECK_EQ(h_klass->IsErroneous(), failure_kind == verifier::FailureKind::kHardFailure);
- if (failure_kind == verifier::FailureKind::kHardFailure) {
- passed_class_verification = false;
- // ClassLinker::VerifyClass throws, so we clear it before we continue.
- CHECK(soa.Self()->IsExceptionPending());
- soa.Self()->ClearException();
- }
-
- CHECK(h_klass->ShouldVerifyAtRuntime() ||
- h_klass->IsVerifiedNeedsAccessChecks() ||
- h_klass->IsVerified() ||
- h_klass->IsErroneous())
- << h_klass->PrettyDescriptor() << ": state=" << h_klass->GetStatus();
-
- soa.Self()->AssertNoPendingException();
+ for (ClassAccessor accessor : dex_file->GetClasses()) {
+ h_klass.Assign(
+ class_linker->FindClass(soa.Self(), *dex_file, accessor.GetClassIdx(), h_loader));
+ // Ignore classes that couldn't be loaded since we are looking for crashes during
+ // class/method verification.
+ if (h_klass == nullptr || h_klass->IsErroneous()) {
+ // Treat as failure to pass verification
+ passed_class_verification = false;
+ soa.Self()->ClearException();
+ continue;
}
+ if (&h_klass->GetDexFile() != dex_file) {
+ // Skip a duplicate class (as the resolved class is from another dex file). This can happen
+ // e.g. if we have a class named "sun.misc.Unsafe" in fuzz.dex.
+ continue;
+ }
+
+ h_dex_cache.Assign(h_klass->GetDexCache());
+
+ // The class loader from the class's dex cache is different from the dex file's class loader
+ // for boot image classes e.g. java.util.AbstractCollection.
+ h_dex_cache_class_loader.Assign(h_klass->GetDexCache()->GetClassLoader());
+
+ CHECK(h_klass->IsResolved()) << h_klass->PrettyClass();
+ verifier::FailureKind failure_kind =
+ class_linker->VerifyClass(soa.Self(),
+ /* verifier_deps= */ nullptr,
+ h_klass,
+ // Don't abort on verification errors.
+ verifier::HardFailLogMode::kLogWarning);
+
+ DCHECK_EQ(h_klass->IsErroneous(), failure_kind == verifier::FailureKind::kHardFailure);
+ if (failure_kind == verifier::FailureKind::kHardFailure) {
+ passed_class_verification = false;
+ // ClassLinker::VerifyClass throws, so we clear it before we continue.
+ CHECK(soa.Self()->IsExceptionPending());
+ soa.Self()->ClearException();
+ }
+
+ CHECK(h_klass->ShouldVerifyAtRuntime() || h_klass->IsVerifiedNeedsAccessChecks() ||
+ h_klass->IsVerified() || h_klass->IsErroneous())
+ << h_klass->PrettyDescriptor() << ": state=" << h_klass->GetStatus();
+
+ soa.Self()->AssertNoPendingException();
}
+ return passed_class_verification;
+}
+
+ALWAYS_INLINE void IterationCleanup(jobject class_loader, const StandardDexFile* dex_file) {
+ ScopedObjectAccess soa(Thread::Current());
+ Runtime* runtime = Runtime::Current();
// Clear the arena pool to free RAM. The next iteration won't be referencing the pool we just
// used.
- Runtime::Current()->ReclaimArenaPoolMemory();
+ runtime->ReclaimArenaPoolMemory();
// Delete weak root to the DexCache before removing a DEX file from the cache. This is usually
// handled by the GC, but since we are not calling it every iteration, we need to delete them
@@ -220,12 +217,10 @@
// Mimic DexFile_closeDexFile.
RemoveNativeDebugInfoForDex(soa.Self(), dex_file);
- class_linker->RemoveDexFromCaches(*dex_file);
+ runtime->GetClassLinker()->RemoveDexFromCaches(*dex_file);
// Delete global ref and unload class loader to free RAM.
soa.Env()->GetVm()->DeleteGlobalRef(soa.Self(), class_loader);
-
- return passed_class_verification;
}
jobject RegisterDexFileAndGetClassLoader(Runtime* runtime, const StandardDexFile* dex_file) {
@@ -239,5 +234,134 @@
return class_loader;
}
+std::unique_ptr<CompilerOptions> CreateCompilerOptions() {
+ std::unique_ptr<CompilerOptions> opt = std::make_unique<CompilerOptions>();
+ opt->emit_read_barrier_ = gUseReadBarrier;
+ opt->instruction_set_ =
+ (kRuntimeISA == InstructionSet::kArm) ? InstructionSet::kThumb2 : kRuntimeISA;
+ std::unique_ptr<const InstructionSetFeatures> kISAFeatures =
+ InstructionSetFeatures::FromCppDefines();
+ CHECK(kISAFeatures != nullptr);
+ CHECK_EQ(kRuntimeISA, kISAFeatures->GetInstructionSet());
+ opt->instruction_set_features_ =
+ InstructionSetFeatures::FromBitmap(kRuntimeISA, kISAFeatures->AsBitmap());
+ CHECK(opt->instruction_set_features_ != nullptr);
+ opt->implicit_null_checks_ = true;
+ opt->implicit_so_checks_ = true;
+ opt->implicit_suspend_checks_ = kRuntimeISA == InstructionSet::kArm64;
+ return opt;
+}
+
+Compiler* CreateCompiler(const CompilerOptions& compiler_options,
+ CompiledCodeStorage* storage) {
+ // Consistency checks
+ CHECK(!compiler_options.GetDebuggable());
+ CHECK(!kUseTableLookupReadBarrier);
+ CHECK(kReserveMarkingRegister);
+ CHECK(!kPoisonHeapReferences);
+ // TODO(solanes): parametrize the compilation kind and get kBaseline for free.
+ CHECK(!compiler_options.IsBaseline());
+
+ // Testing AOT compiler.
+ CHECK_EQ(Runtime::Current()->GetJit(), nullptr);
+
+ return Compiler::Create(compiler_options, storage);
+}
+
+ALWAYS_INLINE bool CompileClasses(jobject class_loader,
+ const StandardDexFile* dex_file,
+ Compiler* compiler,
+ FuzzerCompilerCallbacks* callbacks,
+ bool debug_prints) {
+ bool at_least_one_method_called_the_compiler = false;
+ Runtime* runtime = Runtime::Current();
+ ClassLinker* class_linker = runtime->GetClassLinker();
+
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<4> scope(soa.Self());
+ Handle<mirror::ClassLoader> h_loader =
+ scope.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
+ MutableHandle<mirror::Class> h_klass(scope.NewHandle<mirror::Class>(nullptr));
+ MutableHandle<mirror::DexCache> h_dex_cache(scope.NewHandle<mirror::DexCache>(nullptr));
+ MutableHandle<mirror::ClassLoader> h_dex_cache_class_loader = scope.NewHandle(h_loader.Get());
+
+ // We finished verification and we can move to compilation, using the verification results
+ for (ClassAccessor accessor : dex_file->GetClasses()) {
+ h_klass.Assign(
+ class_linker->FindClass(soa.Self(), *dex_file, accessor.GetClassIdx(), h_loader));
+ // Ignore classes that couldn't be loaded since we are looking for crashes during
+ // compilation.
+ if (h_klass == nullptr || h_klass->IsErroneous()) {
+ soa.Self()->ClearException();
+ continue;
+ }
+ if (&h_klass->GetDexFile() != dex_file) {
+ // Skip a duplicate class (as the resolved class is from another dex file). This can happen
+ // e.g. if we have a class named "sun.misc.Unsafe" in fuzz.dex.
+ continue;
+ }
+
+ ClassReference ref(dex_file, h_klass->GetDexClassDefIndex());
+ if (callbacks->IsClassRejected(ref)) {
+ continue;
+ }
+
+ h_dex_cache.Assign(h_klass->GetDexCache());
+
+ // The class loader from the class's dex cache is different from the dex file's class loader
+ // for boot image classes e.g. java.util.AbstractCollection.
+ h_dex_cache_class_loader.Assign(h_klass->GetDexCache()->GetClassLoader());
+
+ int64_t previous_method_idx = -1;
+ for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+ const uint32_t method_idx = method.GetIndex();
+ if (method_idx == previous_method_idx) {
+ // Duplicate method
+ continue;
+ }
+ previous_method_idx = method_idx;
+
+ const uint32_t access_flags = method.GetAccessFlags();
+
+ if (ArtMethod::IsNative(access_flags)) {
+ // TODO(solanes): Support JNI?
+ continue;
+ }
+
+ if (ArtMethod::IsAbstract(access_flags)) {
+ // Abstract methods don't have code.
+ continue;
+ }
+
+ // TODO(solanes): If we want to support the fast compiler, add `!method.IsInvokable() ||`.
+ if (!ArtMethod::IsCompilable(access_flags) || ArtMethod::IsIntrinsic(access_flags)) {
+ // This method will never be compiled.
+ continue;
+ }
+
+ MethodReference method_ref(dex_file, method.GetIndex());
+ if (debug_prints) {
+ LOG(ERROR) << "Going to compile: " << dex_file->PrettyMethod(method.GetIndex())
+ << ". IsUncompilableMethod: " << std::boolalpha
+ << callbacks->IsUncompilableMethod(method_ref) << std::noboolalpha
+ << " using klass " << h_klass->PrettyClass();
+ }
+
+ if (callbacks->IsUncompilableMethod(method_ref)) {
+ continue;
+ }
+ at_least_one_method_called_the_compiler = true;
+ compiler->Compile(method.GetCodeItem(),
+ access_flags,
+ accessor.GetClassDefIndex(),
+ method.GetIndex(),
+ h_dex_cache_class_loader,
+ *dex_file,
+ h_dex_cache);
+ }
+ }
+ return at_least_one_method_called_the_compiler;
+}
+
} // namespace fuzzer
} // namespace art
diff --git a/tools/fuzzer/fuzzer_common.h b/tools/fuzzer/fuzzer_common.h
index 2bb6793..3dc3600 100644
--- a/tools/fuzzer/fuzzer_common.h
+++ b/tools/fuzzer/fuzzer_common.h
@@ -25,11 +25,18 @@
#include "base/file_utils.h"
#include "base/mem_map.h"
#include "class_linker.h"
+#include "compiler.h"
+#include "dex/class_accessor-inl.h"
+#include "dex/class_reference.h"
#include "dex/dex_file_verifier.h"
+#include "dex/method_reference.h"
#include "dex/standard_dex_file.h"
+#include "driver/compiled_code_storage.h"
+#include "driver/compiler_options.h"
#include "interpreter/unstarted_runtime.h"
#include "jit/debugger_interface.h"
#include "jni/java_vm_ext.h"
+#include "noop_compiler_callbacks.h"
#include "runtime.h"
#include "verifier/class_verifier.h"
#include "well_known_classes.h"
@@ -37,12 +44,16 @@
namespace art {
namespace fuzzer {
+// Dex file verification
+
// Returns the DEX file created using the parameters, if it could be verified. Returns nullptr if
// the DEX file couldn't be verified.
ALWAYS_INLINE std::unique_ptr<StandardDexFile> VerifyDexFile(const uint8_t* data,
size_t size,
const std::string& location);
+// Class verification
+
// A class to be friends with ClassLinker and access the internal FindDexCacheDataLocked method.
class FuzzerCommonHelper {
public:
@@ -58,7 +69,89 @@
jobject RegisterDexFileAndGetClassLoader(Runtime* runtime, const StandardDexFile* dex_file);
// Initialization for all fuzzers that need a Runtime.
-void FuzzerInitialize();
+void FuzzerInitialize(CompilerCallbacks* callbacks);
+
+// Cleans up memory after the iteration ran.
+ALWAYS_INLINE void IterationCleanup(jobject class_loader, const StandardDexFile* dex_file);
+
+// Compilation
+
+// Storage that will fake setting the thunk code so that the fuzzer can call Emit and test that code
+// path.
+class FuzzerCompiledMethodStorage final : public CompiledCodeStorage {
+ public:
+ FuzzerCompiledMethodStorage() {}
+ ~FuzzerCompiledMethodStorage() {}
+
+ CompiledMethod* CreateCompiledMethod(InstructionSet instruction_set,
+ ArrayRef<const uint8_t> code,
+ ArrayRef<const uint8_t> stack_map,
+ [[maybe_unused]] ArrayRef<const uint8_t> cfi,
+ [[maybe_unused]] ArrayRef<const linker::LinkerPatch> patches,
+ [[maybe_unused]] bool is_intrinsic) override {
+ DCHECK_NE(instruction_set, InstructionSet::kNone);
+ DCHECK(!code.empty());
+ DCHECK(!stack_map.empty());
+ return reinterpret_cast<CompiledMethod*>(this);
+ }
+
+ ArrayRef<const uint8_t> GetThunkCode([[maybe_unused]] const linker::LinkerPatch& patch,
+ [[maybe_unused]] /*out*/ std::string* debug_name) override {
+ return ArrayRef<const uint8_t>();
+ }
+
+ void SetThunkCode([[maybe_unused]] const linker::LinkerPatch& patch,
+ [[maybe_unused]] ArrayRef<const uint8_t> code,
+ [[maybe_unused]] const std::string& debug_name) override {}
+};
+
+class FuzzerCompilerCallbacks final : public NoopCompilerCallbacks {
+ public:
+ FuzzerCompilerCallbacks() {}
+
+ void AddUncompilableMethod(MethodReference ref) override { uncompilable_methods_.insert(ref); }
+
+ void AddUncompilableClass(ClassReference ref) override {
+ const DexFile& dex_file = *ref.dex_file;
+ const dex::ClassDef& class_def = dex_file.GetClassDef(ref.ClassDefIdx());
+ ClassAccessor accessor(dex_file, class_def);
+ for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+ MethodReference method_ref(&dex_file, method.GetIndex());
+ AddUncompilableMethod(method_ref);
+ }
+ }
+
+ void ClassRejected(ClassReference ref) override { rejected_classes_.insert(ref); }
+
+ bool IsUncompilableMethod(MethodReference ref) override {
+ return uncompilable_methods_.find(ref) != uncompilable_methods_.end();
+ }
+
+ bool IsClassRejected(ClassReference ref) {
+ return rejected_classes_.find(ref) != rejected_classes_.end();
+ }
+
+ void Reset() {
+ uncompilable_methods_.clear();
+ rejected_classes_.clear();
+ }
+
+ private:
+ std::set<MethodReference> uncompilable_methods_;
+ std::set<ClassReference> rejected_classes_;
+};
+
+// Compiles all classes within a DEX file. Returns true iff at least one method called the compiler.
+ALWAYS_INLINE bool CompileClasses(jobject class_loader,
+ const StandardDexFile* dex_file,
+ Compiler* compiler,
+ FuzzerCompilerCallbacks* callbacks,
+ bool debug_prints);
+
+// Creates a compiler. As part of this, creates and sets `compiler_options`.
+Compiler* CreateCompiler(const CompilerOptions& compiler_options,
+ CompiledCodeStorage* storage);
+std::unique_ptr<CompilerOptions> CreateCompilerOptions();
} // namespace fuzzer
} // namespace art
diff --git a/tools/fuzzer/libart_optimizing_compiler_fuzzer.cc b/tools/fuzzer/libart_optimizing_compiler_fuzzer.cc
new file mode 100644
index 0000000..a6d1f0b
--- /dev/null
+++ b/tools/fuzzer/libart_optimizing_compiler_fuzzer.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#include <vector>
+
+#include "compiler.h"
+#include "dex/standard_dex_file.h"
+#include "driver/compiler_options.h"
+#include "fuzzer_common.h"
+#include "gc/heap.h"
+#include "runtime.h"
+
+namespace art {
+namespace fuzzer {
+
+// Global variable to count how many DEX files passed DEX file verification and they were
+// registered, since these are the cases for which we would be running the GC. In case of
+// scheduling multiple fuzzer jobs, using the ‘-jobs’ flag, this is not shared among the threads.
+int skipped_gc_iterations = 0;
+// Global variable to call the GC once every maximum number of iterations.
+// TODO: These values were obtained from local experimenting. They can be changed after
+// further investigation.
+static constexpr int kMaxSkipGCIterations = 3000;
+
+// Extra debugging information.
+static constexpr bool kDebugPrints = false;
+
+std::vector<std::unique_ptr<uint8_t[]>> data_to_delete;
+std::vector<std::unique_ptr<StandardDexFile>> dex_files_to_delete;
+
+std::unique_ptr<Compiler> compiler;
+std::unique_ptr<CompilerOptions> compiler_options;
+std::unique_ptr<FuzzerCompilerCallbacks> callbacks;
+// No need for unique_ptr as it is just a fake storage.
+FuzzerCompiledMethodStorage storage;
+
+extern "C" int LLVMFuzzerInitialize([[maybe_unused]] int* argc, [[maybe_unused]] char*** argv) {
+ callbacks.reset(new FuzzerCompilerCallbacks());
+ FuzzerInitialize(callbacks.get());
+ compiler_options = CreateCompilerOptions();
+ compiler.reset(CreateCompiler(*compiler_options, &storage));
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ // Note that we ignore the resulting DEX file from `VerifyDexFile` since we want to copy `data` to
+ // manage it ourselves.
+ if (VerifyDexFile(data, size, "fuzz.dex") == nullptr) {
+ // DEX file couldn't be verified, don't save it in the corpus.
+ return -1;
+ }
+
+ // Copy data to keep it alive. Use unique_ptr so that we don't leak.
+ data_to_delete.emplace_back(new uint8_t[size]);
+ uint8_t* new_data = data_to_delete.back().get();
+ memcpy(new_data, data, size);
+
+ dex_files_to_delete.emplace_back(
+ new StandardDexFile(new_data,
+ /*location=*/"fuzz.dex",
+ /*location_checksum=*/0,
+ /*oat_dex_file=*/nullptr,
+ std::make_shared<MemoryDexFileContainer>(new_data, size)));
+ StandardDexFile* dex_file = dex_files_to_delete.back().get();
+
+ Runtime* runtime = Runtime::Current();
+ CHECK(runtime != nullptr);
+
+ jobject class_loader = RegisterDexFileAndGetClassLoader(runtime, dex_file);
+
+ VerifyClasses(class_loader, dex_file);
+ const bool at_least_one_method_called_the_compiler =
+ CompileClasses(class_loader, dex_file, compiler.get(), callbacks.get(), kDebugPrints);
+ callbacks->Reset();
+ IterationCleanup(class_loader, dex_file);
+
+ if (skipped_gc_iterations == kMaxSkipGCIterations) {
+ runtime->GetHeap()->CollectGarbage(/* clear_soft_references */ true);
+ skipped_gc_iterations = 0;
+ data_to_delete.clear();
+ dex_files_to_delete.clear();
+ } else {
+ skipped_gc_iterations++;
+ }
+
+ // Save only if at least one method compiled
+ return at_least_one_method_called_the_compiler ? 0 : -1;
+}
+
+} // namespace fuzzer
+} // namespace art
diff --git a/tools/fuzzer/libart_verify_classes_fuzzer.cc b/tools/fuzzer/libart_verify_classes_fuzzer.cc
index 5e2e336..0406e25 100644
--- a/tools/fuzzer/libart_verify_classes_fuzzer.cc
+++ b/tools/fuzzer/libart_verify_classes_fuzzer.cc
@@ -15,14 +15,12 @@
*/
#include <cstdint>
-#include <iostream>
#include <vector>
-#include "base/mem_map.h"
+#include "dex/standard_dex_file.h"
#include "fuzzer_common.h"
-#include "handle_scope-inl.h"
+#include "gc/heap.h"
#include "runtime.h"
-#include "scoped_thread_state_change-inl.h"
namespace art {
namespace fuzzer {
@@ -37,10 +35,11 @@
static constexpr int kMaxSkipGCIterations = 3000;
std::vector<std::unique_ptr<uint8_t[]>> data_to_delete;
-std::vector<std::unique_ptr<art::StandardDexFile>> dex_files_to_delete;
+std::vector<std::unique_ptr<StandardDexFile>> dex_files_to_delete;
extern "C" int LLVMFuzzerInitialize([[maybe_unused]] int* argc, [[maybe_unused]] char*** argv) {
- FuzzerInitialize();
+ static NoopCompilerCallbacks callbacks;
+ FuzzerInitialize(&callbacks);
return 0;
}
@@ -58,19 +57,20 @@
memcpy(new_data, data, size);
dex_files_to_delete.emplace_back(
- new art::StandardDexFile(new_data,
- /*location=*/"fuzz.dex",
- /*location_checksum=*/0,
- /*oat_dex_file=*/nullptr,
- std::make_shared<art::MemoryDexFileContainer>(new_data, size)));
- art::StandardDexFile* dex_file = dex_files_to_delete.back().get();
+ new StandardDexFile(new_data,
+ /*location=*/"fuzz.dex",
+ /*location_checksum=*/0,
+ /*oat_dex_file=*/nullptr,
+ std::make_shared<MemoryDexFileContainer>(new_data, size)));
+ StandardDexFile* dex_file = dex_files_to_delete.back().get();
- art::Runtime* runtime = art::Runtime::Current();
+ Runtime* runtime = Runtime::Current();
CHECK(runtime != nullptr);
jobject class_loader = RegisterDexFileAndGetClassLoader(runtime, dex_file);
VerifyClasses(class_loader, dex_file);
+ IterationCleanup(class_loader, dex_file);
if (skipped_gc_iterations == kMaxSkipGCIterations) {
runtime->GetHeap()->CollectGarbage(/* clear_soft_references */ true);
diff --git a/tools/fuzzer/optimized-compiler-corpus/Main.dex b/tools/fuzzer/optimized-compiler-corpus/Main.dex
new file mode 100644
index 0000000..ec29035
--- /dev/null
+++ b/tools/fuzzer/optimized-compiler-corpus/Main.dex
Binary files differ
diff --git a/tools/fuzzer/optimized-compiler-corpus/hello_world.dex b/tools/fuzzer/optimized-compiler-corpus/hello_world.dex
new file mode 100644
index 0000000..6e59dd7
--- /dev/null
+++ b/tools/fuzzer/optimized-compiler-corpus/hello_world.dex
Binary files differ