Skip to content

Commit 0e2831d

Browse files
authored
Obfuscated sample app with different crashes (#1187)
* working de obfuscation and proguard rules file * added two new fragments with a range of crash options * restore agp version and removed extra files * spotless apply * fix formatting * added simple crash button, removed some verbose comments, cleaned up code * added comment
1 parent 0b3a32d commit 0e2831d

11 files changed

+559
-5
lines changed

gradle.properties

+3
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ android.useAndroidX=true
1919
# generate the BuildConfig class that contains the app version
2020
version=1.10.0
2121
group=com.splunk
22+
# uncomment to test r8 obfuscation without full mode (less aggressive)
23+
#android.enableR8.fullMode=false
24+

sample-app/build.gradle.kts

+1-4
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,9 @@ android {
4242
val accessToken = localProperties["rum.access.token"] as String?
4343
resValue("string", "rum_realm", realm ?: "us0")
4444
resValue("string", "rum_access_token", accessToken ?: "dummyAuth")
45-
isMinifyEnabled = false
45+
isMinifyEnabled = false // set to true to enable obfuscation
4646
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
4747
}
48-
49-
release {
50-
}
5148
}
5249

5350
compileOptions {

sample-app/proguard-rules.pro

+22-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,31 @@
1212
# public *;
1313
#}
1414

15+
# Ignore missing class warnings from dependencies
16+
-dontwarn com.fasterxml.jackson.**
17+
-dontwarn com.google.auto.value.**
18+
-dontwarn com.google.errorprone.**
19+
-dontwarn com.google.common.io.**
20+
-dontwarn io.grpc.**
21+
-dontwarn org.osgi.annotation.**
22+
1523
# Uncomment this to preserve the line number information for
1624
# debugging stack traces.
1725
#-keepattributes SourceFile,LineNumberTable
1826

1927
# If you keep the line number information, uncomment this to
2028
# hide the original source file name.
21-
#-renamesourcefileattribute SourceFile
29+
#-renamesourcefileattribute SourceFile
30+
31+
# Preserve details needed for reflection, annotations and lambdas
32+
#-keepattributes Signature,InnerClasses,EnclosingMethod
33+
#-keepattributes MethodParameters,Exceptions
34+
#-keepattributes *Annotation*
35+
36+
# Force R8/ProGuard to keep package structure
37+
#-keeppackagenames
38+
39+
# Allows changing access modifiers to enable more optimizations
40+
#-allowaccessmodification
41+
# Uses same names for different methods when possible
42+
#-overloadaggressively
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.android.sample;
18+
19+
import android.os.Bundle;
20+
import android.view.LayoutInflater;
21+
import android.view.View;
22+
import android.view.ViewGroup;
23+
import android.widget.Button;
24+
import androidx.annotation.NonNull;
25+
import androidx.annotation.Nullable;
26+
import androidx.fragment.app.Fragment;
27+
import androidx.navigation.fragment.NavHostFragment;
28+
import java.io.IOException;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
import java.util.concurrent.Callable;
32+
import java.util.concurrent.CountDownLatch;
33+
34+
public class CrashTestFragment extends Fragment {
35+
36+
@Nullable
37+
@Override
38+
public View onCreateView(
39+
@NonNull LayoutInflater inflater,
40+
@Nullable ViewGroup container,
41+
@Nullable Bundle savedInstanceState) {
42+
return inflater.inflate(R.layout.fragment_crash_test, container, false);
43+
}
44+
45+
@Override
46+
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
47+
super.onViewCreated(view, savedInstanceState);
48+
49+
Button simpleCrashButton = view.findViewById(R.id.button_simple_crash);
50+
simpleCrashButton.setOnClickListener(v -> simpleCrash());
51+
52+
Button chainedExceptionButton = view.findViewById(R.id.button_chained_exception);
53+
chainedExceptionButton.setOnClickListener(v -> triggerChainedException());
54+
55+
Button syntheticCodeButton = view.findViewById(R.id.button_synthetic_code);
56+
syntheticCodeButton.setOnClickListener(v -> triggerSyntheticCodeException());
57+
58+
Button raceConditionButton = view.findViewById(R.id.button_race_condition);
59+
raceConditionButton.setOnClickListener(v -> triggerRaceConditionCrash());
60+
61+
Button anrButton = view.findViewById(R.id.button_anr);
62+
anrButton.setOnClickListener(v -> triggerANR());
63+
64+
Button navigateButton = view.findViewById(R.id.button_to_fragment_b);
65+
navigateButton.setOnClickListener(
66+
v -> {
67+
NavHostFragment.findNavController(CrashTestFragment.this)
68+
.navigate(R.id.action_CrashTestFragment_to_CrashTestFragmentB);
69+
});
70+
}
71+
72+
private void simpleCrash() {
73+
throw new RuntimeException("Simple RuntimeException Crash");
74+
}
75+
76+
private void triggerChainedException() {
77+
try {
78+
try {
79+
try {
80+
List<String> list = new ArrayList<>();
81+
String item = list.get(10); // cause IndexOutOfBoundsException
82+
} catch (IndexOutOfBoundsException e) {
83+
throw new IOException("Error accessing data", e);
84+
}
85+
} catch (IOException e) {
86+
throw new RuntimeException("Operation failed", e);
87+
}
88+
} catch (RuntimeException e) {
89+
throw new IllegalStateException("Application error occurred", e);
90+
}
91+
}
92+
93+
private void triggerSyntheticCodeException() {
94+
Callable<String> callable1 =
95+
() -> {
96+
Callable<Integer> callable2 =
97+
() -> {
98+
String nullStr = null;
99+
return nullStr.length(); // cause NPE
100+
};
101+
102+
try {
103+
return "Result: " + callable2.call();
104+
} catch (Exception e) {
105+
throw new RuntimeException("Nested lambda failure", e);
106+
}
107+
};
108+
109+
try {
110+
callable1.call();
111+
} catch (Exception e) {
112+
throw new RuntimeException("Synthetic method crash", e);
113+
}
114+
}
115+
116+
private void triggerRaceConditionCrash() {
117+
final List<String> sharedList = new ArrayList<>();
118+
final CountDownLatch startSignal = new CountDownLatch(1);
119+
final int threadCount = 5;
120+
121+
for (int i = 0; i < threadCount; i++) {
122+
final int threadNum = i;
123+
new Thread(
124+
() -> {
125+
try {
126+
startSignal.await();
127+
128+
for (int j = 0; j < 1000; j++) {
129+
sharedList.add("Thread " + threadNum + " value " + j);
130+
if (!sharedList.isEmpty()) {
131+
sharedList.remove(0);
132+
}
133+
}
134+
} catch (Exception e) {
135+
throw new RuntimeException(
136+
"Thread " + threadNum + " crashed", e);
137+
}
138+
},
139+
"CrashTest-Thread-" + i)
140+
.start();
141+
}
142+
startSignal.countDown();
143+
}
144+
145+
private void triggerANR() {
146+
try {
147+
Thread.sleep(6000);
148+
} catch (InterruptedException e) {
149+
Thread.currentThread().interrupt();
150+
}
151+
}
152+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.android.sample;
18+
19+
import android.os.Bundle;
20+
import android.view.LayoutInflater;
21+
import android.view.View;
22+
import android.view.ViewGroup;
23+
import android.widget.Button;
24+
import androidx.annotation.NonNull;
25+
import androidx.annotation.Nullable;
26+
import androidx.fragment.app.Fragment;
27+
import java.lang.reflect.Method;
28+
import java.util.ArrayList;
29+
import java.util.HashMap;
30+
import java.util.Map;
31+
32+
public class CrashTestFragmentB extends Fragment {
33+
34+
@Nullable
35+
@Override
36+
public View onCreateView(
37+
@NonNull LayoutInflater inflater,
38+
@Nullable ViewGroup container,
39+
@Nullable Bundle savedInstanceState) {
40+
return inflater.inflate(R.layout.fragment_crash_test_b, container, false);
41+
}
42+
43+
@Override
44+
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
45+
super.onViewCreated(view, savedInstanceState);
46+
47+
Button inliningButton = view.findViewById(R.id.button_inlining);
48+
inliningButton.setOnClickListener(v -> triggerInliningException());
49+
50+
Button reflectionButton = view.findViewById(R.id.button_reflection);
51+
reflectionButton.setOnClickListener(v -> triggerReflectionException());
52+
53+
Button inheritanceButton = view.findViewById(R.id.button_inheritance);
54+
inheritanceButton.setOnClickListener(v -> triggerInheritanceException());
55+
56+
Button oomButton = view.findViewById(R.id.button_oom);
57+
oomButton.setOnClickListener(v -> triggerOutOfMemoryError());
58+
59+
Button overloadButton = view.findViewById(R.id.button_overload);
60+
overloadButton.setOnClickListener(v -> triggerOverloadedMethodCrash());
61+
}
62+
63+
private void triggerInliningException() {
64+
int a = inlinableMethod1(5);
65+
int b = inlinableMethod2(0);
66+
int result = a / b; // Division by zero
67+
}
68+
69+
private int inlinableMethod1(int value) {
70+
return value * 5;
71+
}
72+
73+
private int inlinableMethod2(int value) {
74+
return value;
75+
}
76+
77+
private void triggerReflectionException() {
78+
try {
79+
Method method = String.class.getMethod("nonExistentMethod");
80+
81+
// won't reach here
82+
method.invoke("test");
83+
} catch (Exception e) {
84+
throw new RuntimeException("Reflection failed", e);
85+
}
86+
}
87+
88+
private void triggerInheritanceException() {
89+
DeepestChild deepChild = new DeepestChild();
90+
91+
deepChild.methodThatThrows();
92+
}
93+
94+
private static class BaseClass {
95+
protected void baseMethod() {
96+
throw new RuntimeException("Exception in base class");
97+
}
98+
}
99+
100+
private static class FirstChild extends BaseClass {
101+
@Override
102+
protected void baseMethod() {
103+
try {
104+
super.baseMethod();
105+
} catch (RuntimeException e) {
106+
throw new IllegalStateException("Exception in first child", e);
107+
}
108+
}
109+
}
110+
111+
private static class SecondChild extends FirstChild {
112+
protected void midMethod() {
113+
baseMethod();
114+
}
115+
}
116+
117+
private static class DeepestChild extends SecondChild {
118+
public void methodThatThrows() {
119+
midMethod();
120+
}
121+
}
122+
123+
private void triggerOutOfMemoryError() {
124+
try {
125+
ArrayList<byte[]> memoryHog = new ArrayList<>();
126+
127+
// Allocating memory in chunks until out of memory
128+
for (int i = 0; i < 100; i++) {
129+
memoryHog.add(new byte[10 * 1024 * 1024]); // 10MB chunks
130+
}
131+
} catch (OutOfMemoryError e) {
132+
throw e;
133+
}
134+
}
135+
136+
private void triggerOverloadedMethodCrash() {
137+
crashingMethod("test");
138+
}
139+
140+
private void crashingMethod(String param) {
141+
crashingMethod(param, 10);
142+
}
143+
144+
private void crashingMethod(String param, int value) {
145+
crashingMethod(param, value, true);
146+
}
147+
148+
private void crashingMethod(String param, int value, boolean flag) {
149+
crashingMethod(param, value, flag, new HashMap<>());
150+
}
151+
152+
private void crashingMethod(String param, int value, boolean flag, Map<String, Object> extra) {
153+
if (param != null && value > 0 && flag) {
154+
throw new IllegalArgumentException(
155+
"Crasher called with: " + param + ", " + value + ", " + flag);
156+
}
157+
}
158+
}

sample-app/src/main/java/com/splunk/android/sample/FirstFragment.java

+5
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
135135
"kustom",
136136
Attributes.of(longKey("counted"), customCount.incrementAndGet()));
137137
});
138+
binding.crashTestFragment.setOnClickListener(
139+
v -> {
140+
NavHostFragment.findNavController(FirstFragment.this)
141+
.navigate(R.id.action_FirstFragment_to_CrashTestFragment);
142+
});
138143
}
139144

140145
private void multiThreadCrashing() {

0 commit comments

Comments
 (0)