1 // Copyright 2021 Code Intelligence GmbH
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "jvm_tooling.h"
16
17 #include "coverage_tracker.h"
18 #include "fuzz_target_runner.h"
19 #include "gflags/gflags.h"
20 #include "gtest/gtest.h"
21 #include "tools/cpp/runfiles/runfiles.h"
22
23 DECLARE_string(cp);
24 DECLARE_string(jvm_args);
25 DECLARE_string(target_class);
26 DECLARE_string(target_args);
27 DECLARE_string(agent_path);
28 DECLARE_string(instrumentation_excludes);
29
30 #ifdef _WIN32
31 #define ARG_SEPARATOR ";"
32 #else
33 #define ARG_SEPARATOR ":"
34 #endif
35
36 namespace jazzer {
37
38 std::vector<std::string> splitOnSpace(const std::string &s);
39
TEST(SpaceSplit,SpaceSplitSimple)40 TEST(SpaceSplit, SpaceSplitSimple) {
41 ASSERT_EQ((std::vector<std::string>{"first", "se\\ cond", "third"}),
42 splitOnSpace("first se\\ cond third"));
43 }
44
45 class JvmToolingTest : public ::testing::Test {
46 protected:
47 // After DestroyJavaVM() no new JVM instance can be created in the same
48 // process, so we set up a single JVM instance for this test binary which gets
49 // destroyed after all tests in this test suite have finished.
SetUpTestCase()50 static void SetUpTestCase() {
51 FLAGS_jvm_args =
52 "-Denv1=va\\" ARG_SEPARATOR "l1\\\\" ARG_SEPARATOR "-Denv2=val2";
53 FLAGS_instrumentation_excludes = "**";
54 using ::bazel::tools::cpp::runfiles::Runfiles;
55 Runfiles *runfiles = Runfiles::CreateForTest();
56 FLAGS_cp = runfiles->Rlocation(FLAGS_cp);
57
58 jvm_ = std::make_unique<JVM>("test_executable");
59 }
60
TearDownTestCase()61 static void TearDownTestCase() { jvm_.reset(nullptr); }
62
63 static std::unique_ptr<JVM> jvm_;
64 };
65
66 std::unique_ptr<JVM> JvmToolingTest::jvm_ = nullptr;
67
TEST_F(JvmToolingTest,ClassNotFound)68 TEST_F(JvmToolingTest, ClassNotFound) {
69 ASSERT_THROW(jvm_->FindClass(""), std::runtime_error);
70 ASSERT_THROW(jvm_->FindClass("test.NonExistingClass"), std::runtime_error);
71 ASSERT_THROW(jvm_->FindClass("test/NonExistingClass"), std::runtime_error);
72 }
73
TEST_F(JvmToolingTest,ClassInClassPath)74 TEST_F(JvmToolingTest, ClassInClassPath) {
75 ASSERT_NE(nullptr, jvm_->FindClass("test.PropertyPrinter"));
76 ASSERT_NE(nullptr, jvm_->FindClass("test/PropertyPrinter"));
77 }
78
TEST_F(JvmToolingTest,JniProperties)79 TEST_F(JvmToolingTest, JniProperties) {
80 auto property_printer_class = jvm_->FindClass("test.PropertyPrinter");
81 ASSERT_NE(nullptr, property_printer_class);
82 auto method_id =
83 jvm_->GetStaticMethodID(property_printer_class, "printProperty",
84 "(Ljava/lang/String;)Ljava/lang/String;");
85 ASSERT_NE(nullptr, method_id);
86
87 auto &env = jvm_->GetEnv();
88 for (const auto &el : std::vector<std::pair<std::string, std::string>>{
89 {"not set property", ""},
90 {"env1", "va" ARG_SEPARATOR "l1\\"},
91 {"env2", "val2"}}) {
92 jstring str = env.NewStringUTF(el.first.c_str());
93 auto ret = (jstring)env.CallStaticObjectMethod(property_printer_class,
94 method_id, str);
95 ASSERT_FALSE(env.ExceptionCheck());
96 if (el.second.empty()) {
97 ASSERT_EQ(nullptr, ret);
98 } else {
99 ASSERT_NE(nullptr, ret);
100 jboolean is_copy;
101 ASSERT_EQ(el.second, jvm_->GetEnv().GetStringUTFChars(ret, &is_copy));
102 }
103 }
104 }
105
TEST_F(JvmToolingTest,SimpleFuzzTarget)106 TEST_F(JvmToolingTest, SimpleFuzzTarget) {
107 // see testdata/test/SimpleFuzzTarget.java for the implementation of the fuzz
108 // target
109 FLAGS_target_class = "test/SimpleFuzzTarget";
110 FLAGS_target_args = "";
111 FuzzTargetRunner fuzz_target_runner(*jvm_);
112
113 // normal case: fuzzerTestOneInput returns false
114 std::string input("random");
115 ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(
116 (const uint8_t *)input.c_str(), input.size()));
117
118 // exception is thrown in fuzzerTestOneInput
119 input = "crash";
120 ASSERT_EQ(
121 RunResult::kException,
122 fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size()));
123 }
124
125 class ExceptionPrinterTest : public ExceptionPrinter {
126 public:
ExceptionPrinterTest(JVM & jvm)127 ExceptionPrinterTest(JVM &jvm) : ExceptionPrinter(jvm), jvm_(jvm) {}
128
TriggerJvmException()129 std::string TriggerJvmException() {
130 jclass illegal_argument_exception =
131 jvm_.FindClass("java.lang.IllegalArgumentException");
132 jvm_.GetEnv().ThrowNew(illegal_argument_exception, "Test");
133 jthrowable exception = jvm_.GetEnv().ExceptionOccurred();
134 jvm_.GetEnv().ExceptionClear();
135 return getStackTrace(exception);
136 }
137
138 private:
139 const JVM &jvm_;
140 };
141
TEST_F(JvmToolingTest,ExceptionPrinter)142 TEST_F(JvmToolingTest, ExceptionPrinter) {
143 ExceptionPrinterTest exception_printer(*jvm_);
144 // a.k.a std::string.startsWith(java.lang...)
145 ASSERT_TRUE(exception_printer.TriggerJvmException().rfind(
146 "java.lang.IllegalArgumentException", 0) == 0);
147 }
148
TEST_F(JvmToolingTest,FuzzTargetWithInit)149 TEST_F(JvmToolingTest, FuzzTargetWithInit) {
150 // see testdata/test/FuzzTargetWithInit.java for the implementation of the
151 // fuzz target. All string arguments provided in fuzzerInitialize(String[])
152 // will cause a crash if input in fuzzerTestOneInput(byte[]).
153 FLAGS_target_class = "test/FuzzTargetWithInit";
154 FLAGS_target_args = "crash_now crash_harder";
155 FuzzTargetRunner fuzz_target_runner(*jvm_);
156
157 // normal case: fuzzerTestOneInput returns false
158 std::string input("random");
159 ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(
160 (const uint8_t *)input.c_str(), input.size()));
161
162 input = "crash_now";
163 ASSERT_EQ(
164 RunResult::kException,
165 fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size()));
166
167 input = "this is harmless";
168 ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(
169 (const uint8_t *)input.c_str(), input.size()));
170
171 input = "crash_harder";
172 ASSERT_EQ(
173 RunResult::kException,
174 fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size()));
175 }
176
TEST_F(JvmToolingTest,TestCoverageMap)177 TEST_F(JvmToolingTest, TestCoverageMap) {
178 CoverageTracker::Clear();
179 // check that after the initial clear the first coverage counter is 0
180 auto coverage_counters_array = CoverageTracker::GetCoverageCounters();
181 ASSERT_EQ(0, coverage_counters_array[0]);
182
183 FLAGS_target_class = "test/FuzzTargetWithCoverage";
184 FLAGS_target_args = "";
185 FuzzTargetRunner fuzz_target_runner(*jvm_);
186 // run a fuzz target input which will cause the first coverage counter to
187 // increase
188 fuzz_target_runner.Run(nullptr, 0);
189 ASSERT_EQ(1, coverage_counters_array[0]);
190 CoverageTracker::Clear();
191 // back to initial state
192 ASSERT_EQ(0, coverage_counters_array[0]);
193
194 // calling the fuzz target twice
195 fuzz_target_runner.Run(nullptr, 0);
196 fuzz_target_runner.Run(nullptr, 0);
197 ASSERT_EQ(2, coverage_counters_array[0]);
198 }
199 } // namespace jazzer
200