From 3b4cc43e798a7706701ec7b2de21ba8a2cf91d02 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 23 Oct 2019 17:34:23 +0300 Subject: [PATCH] C: add option to strip off information about call site locations. This decreases executable size significantly. However, this produces obfuscated stack traces which can be deobfuscated using JSON symbol table. --- .../teavm/classlib/java/lang/TThrowable.java | 5 +- .../java/org/teavm/backend/c/CTarget.java | 70 ++++++++++++++++- .../backend/c/generate/CallSiteGenerator.java | 29 +++---- .../backend/c/generate/GenerationContext.java | 8 +- .../intrinsic/ExceptionHandlingIntrinsic.java | 5 ++ .../ExceptionHandlingIntrinsic.java | 2 + .../main/java/org/teavm/common/JsonUtil.java | 53 +++++++++++++ .../information/SourceMapsWriter.java | 36 +-------- .../org/teavm/runtime/ExceptionHandling.java | 77 +++++++++++-------- .../org/teavm/backend/c/definitions.h | 4 + .../java/org/teavm/tooling/TeaVMTool.java | 1 + 11 files changed, 207 insertions(+), 83 deletions(-) create mode 100644 core/src/main/java/org/teavm/common/JsonUtil.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java index 7aa003028..7c72f80aa 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java @@ -111,10 +111,7 @@ public class TThrowable extends RuntimeException { @Unmanaged private static TStackTraceElement[] fillInStackTraceLowLevel() { - int stackSize = ExceptionHandling.callStackSize(); - TStackTraceElement[] stackTrace = new TStackTraceElement[stackSize]; - ExceptionHandling.fillStackTrace((StackTraceElement[]) (Object) stackTrace); - return stackTrace; + return (TStackTraceElement[]) (Object) ExceptionHandling.fillStackTrace(); } @Rename("getMessage") diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index 85833b771..baf102617 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -18,6 +18,10 @@ package org.teavm.backend.c; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -78,6 +82,7 @@ import org.teavm.backend.lowlevel.transform.CoroutineTransformation; import org.teavm.backend.lowlevel.transform.WeakReferenceTransformation; import org.teavm.cache.EmptyMethodNodeCache; import org.teavm.cache.MethodNodeCache; +import org.teavm.common.JsonUtil; import org.teavm.dependency.ClassDependency; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; @@ -169,6 +174,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private SimpleStringPool stringPool; private boolean longjmpUsed = true; private boolean heapDump; + private boolean obfuscated; private List callSites = new ArrayList<>(); public CTarget(NameProvider nameProvider) { @@ -203,6 +209,10 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { this.astCache = astCache; } + public void setObfuscated(boolean obfuscated) { + this.obfuscated = obfuscated; + } + @Override public List getTransformers() { List transformers = new ArrayList<>(); @@ -392,7 +402,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes, intrinsics, generators, asyncMethods::contains, buildTarget, controller.getClassInitializerInfo(), incremental, longjmpUsed, - vmAssertions, vmAssertions || heapDump); + vmAssertions, vmAssertions || heapDump, obfuscated); BufferedCodeWriter specialWriter = new BufferedCodeWriter(false); BufferedCodeWriter configHeaderWriter = new BufferedCodeWriter(false); @@ -410,6 +420,9 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { if (heapDump) { configHeaderWriter.println("#define TEAVM_HEAP_DUMP 1"); } + if (obfuscated) { + configHeaderWriter.println("#define TEAVM_OBFUSCATED 1"); + } ClassGenerator classGenerator = new ClassGenerator(context, tagRegistry, decompiler, controller.getCacheStatus()); @@ -522,6 +535,61 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { ? this.callSites : CallSiteDescriptor.extract(context.getClassSource(), classNames); new CallSiteGenerator(context, writer, includes, "teavm_callSites").generate(callSites); + if (obfuscated) { + generateCallSitesJson(context.getBuildTarget(), callSites); + } + } + + private void generateCallSitesJson(BuildTarget buildTarget, List callSites) { + try (OutputStream output = buildTarget.createResource("callsites.json"); + Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) { + writer.append("[\n"); + boolean first = true; + for (CallSiteDescriptor descriptor : callSites) { + if (!first) { + writer.append(",\n"); + } + first = false; + writer.append("{\"id\":").append(Integer.toString(descriptor.getId())); + writer.append(",\"locations\":["); + org.teavm.model.lowlevel.CallSiteLocation[] locations = descriptor.getLocations(); + if (locations != null) { + boolean firstLocation = true; + for (org.teavm.model.lowlevel.CallSiteLocation location : locations) { + if (!firstLocation) { + writer.append(","); + } + firstLocation = false; + writer.append("{\"class\":"); + appendJsonString(writer, location.getClassName()); + writer.append(",\"method\":"); + appendJsonString(writer, location.getMethodName()); + writer.append(",\"file\":"); + appendJsonString(writer, location.getFileName()); + writer.append(",\"line\":").append(Integer.toString(location.getLineNumber())); + writer.append("}"); + } + } + writer.append("]}"); + } + if (!first) { + writer.append("\n"); + } + writer.append("]"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void appendJsonString(Writer writer, String string) throws IOException { + if (string == null) { + writer.append("null"); + return; + } + + writer.append("\""); + JsonUtil.writeEscapedString(writer, string); + writer.append("\""); } private void generateStrings(BuildTarget buildTarget, GenerationContext context) throws IOException { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java index e8c64da3f..b01a195fc 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java @@ -26,7 +26,6 @@ import org.teavm.model.lowlevel.CallSiteLocation; import org.teavm.model.lowlevel.ExceptionHandlerDescriptor; public class CallSiteGenerator { - private GenerationContext context; private CodeWriter writer; private IncludeManager includes; @@ -79,20 +78,22 @@ public class CallSiteGenerator { first = false; int locationIndex = -1; - CallSiteLocation[] locations = callSite.getLocations(); - if (locations != null && locations.length > 0) { - LocationList prevList = null; - for (int i = locations.length - 1; i >= 0; --i) { - LocationList list = new LocationList(locations[i], prevList); - locationIndex = locationMap.getOrDefault(list, -1); - if (locationIndex < 0) { - locationIndex = this.locations.size(); - locationMap.put(list, locationIndex); - this.locations.add(list); - } else { - list = this.locations.get(locationIndex); + if (!context.isObfuscated()) { + CallSiteLocation[] locations = callSite.getLocations(); + if (locations != null && locations.length > 0) { + LocationList prevList = null; + for (int i = locations.length - 1; i >= 0; --i) { + LocationList list = new LocationList(locations[i], prevList); + locationIndex = locationMap.getOrDefault(list, -1); + if (locationIndex < 0) { + locationIndex = this.locations.size(); + locationMap.put(list, locationIndex); + this.locations.add(list); + } else { + list = this.locations.get(locationIndex); + } + prevList = list; } - prevList = list; } } diff --git a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java index 985676a90..81cecda7d 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java +++ b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java @@ -50,13 +50,14 @@ public class GenerationContext { private boolean longjmp; private boolean vmAssertions; private boolean heapDump; + private boolean obfuscated; public GenerationContext(VirtualTableProvider virtualTableProvider, Characteristics characteristics, DependencyInfo dependencies, StringPool stringPool, NameProvider names, Diagnostics diagnostics, ClassReaderSource classSource, List intrinsics, List generators, Predicate asyncMethods, BuildTarget buildTarget, ClassInitializerInfo classInitializerInfo, boolean incremental, boolean longjmp, boolean vmAssertions, - boolean heapDump) { + boolean heapDump, boolean obfuscated) { this.virtualTableProvider = virtualTableProvider; this.characteristics = characteristics; this.dependencies = dependencies; @@ -73,6 +74,7 @@ public class GenerationContext { this.longjmp = longjmp; this.vmAssertions = vmAssertions; this.heapDump = heapDump; + this.obfuscated = obfuscated; } public void addIntrinsic(Intrinsic intrinsic) { @@ -152,4 +154,8 @@ public class GenerationContext { public boolean isVmAssertions() { return vmAssertions; } + + public boolean isObfuscated() { + return obfuscated; + } } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java index 477d87a2b..a104e8c40 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java @@ -31,6 +31,7 @@ public class ExceptionHandlingIntrinsic implements Intrinsic { case "isJumpSupported": case "jumpToFrame": case "abort": + case "isObfuscated": return true; default: return false; @@ -64,6 +65,10 @@ public class ExceptionHandlingIntrinsic implements Intrinsic { context.includes().addInclude(""); context.writer().print("abort();"); break; + + case "isObfuscated": + context.writer().print("TEAVM_OBFUSCATED"); + break; } } } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java index 61b80e6e1..24e9479e3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java @@ -54,6 +54,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic { case "isJumpSupported": case "jumpToFrame": case "abort": + case "isObfuscated": return true; } return false; @@ -83,6 +84,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic { } case "isJumpSupported": + case "isObfuscated": return new WasmInt32Constant(0); case "jumpToFrame": diff --git a/core/src/main/java/org/teavm/common/JsonUtil.java b/core/src/main/java/org/teavm/common/JsonUtil.java new file mode 100644 index 000000000..bef5c5eba --- /dev/null +++ b/core/src/main/java/org/teavm/common/JsonUtil.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 konsoletyper. + * + * 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. + */ +package org.teavm.common; + +import java.io.IOException; +import java.io.Writer; + +public final class JsonUtil { + private JsonUtil() { + } + + public static void writeEscapedString(Writer output, String str) throws IOException { + for (int i = 0; i < str.length(); ++i) { + char c = str.charAt(i); + switch (c) { + case '\n': + output.write("\\n"); + break; + case '\r': + output.write("\\r"); + break; + case '\t': + output.write("\\t"); + break; + case '\b': + output.write("\\b"); + break; + case '\\': + output.write("\\\\"); + break; + case '"': + output.write("\\\""); + break; + default: + output.write(c); + break; + } + } + } +} diff --git a/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java b/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java index 9bb1555d4..fc3ee7761 100644 --- a/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java +++ b/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java @@ -17,6 +17,7 @@ package org.teavm.debugging.information; import java.io.IOException; import java.io.Writer; +import org.teavm.common.JsonUtil; class SourceMapsWriter { private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -34,10 +35,10 @@ class SourceMapsWriter { public void write(String generatedFile, String sourceRoot, DebugInformation debugInfo) throws IOException { output.write("{\"version\":3"); output.write(",\"file\":\""); - writeEscapedString(generatedFile); + JsonUtil.writeEscapedString(output, generatedFile); output.write("\""); output.write(",\"sourceRoot\":\""); - writeEscapedString(sourceRoot); + JsonUtil.writeEscapedString(output, sourceRoot); output.write("\""); output.write(",\"sources\":["); for (int i = 0; i < debugInfo.fileNames.length; ++i) { @@ -45,7 +46,7 @@ class SourceMapsWriter { output.write(','); } output.write("\""); - writeEscapedString(debugInfo.fileNames[i]); + JsonUtil.writeEscapedString(output, debugInfo.fileNames[i]); output.write("\""); } output.write("]"); @@ -85,35 +86,6 @@ class SourceMapsWriter { first = false; } - private void writeEscapedString(String str) throws IOException { - for (int i = 0; i < str.length(); ++i) { - char c = str.charAt(i); - switch (c) { - case '\n': - output.write("\\n"); - break; - case '\r': - output.write("\\r"); - break; - case '\t': - output.write("\\t"); - break; - case '\b': - output.write("\\b"); - break; - case '\\': - output.write("\\\\"); - break; - case '"': - output.write("\\\""); - break; - default: - output.write(c); - break; - } - } - } - private void writeVLQ(int number) throws IOException { if (number < 0) { number = ((-number) << 1) | 1; diff --git a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java index 793144e17..cadba5430 100644 --- a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java +++ b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java @@ -37,33 +37,42 @@ public final class ExceptionHandling { @Unmanaged public static native void abort(); + @Unmanaged + private static native boolean isObfuscated(); + @Unmanaged public static void printStack() { Address stackFrame = ShadowStack.getStackTop(); while (stackFrame != null) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); - CallSite callSite = findCallSiteById(callSiteId, stackFrame); - CallSiteLocation location = callSite.location; - while (location != null) { - MethodLocation methodLocation = location != null ? location.method : null; - - Console.printString(" at "); - if (methodLocation.className == null || methodLocation.methodName == null) { - Console.printString("(Unknown method)"); - } else { - Console.printString(methodLocation.className.value); - Console.printString("."); - Console.printString(methodLocation.methodName.value); - } - Console.printString("("); - if (methodLocation.fileName != null && location.lineNumber >= 0) { - Console.printString(methodLocation.fileName.value); - Console.printString(":"); - Console.printInt(location.lineNumber); - } + if (isObfuscated()) { + Console.printString(" at Obfuscated.obfuscated(Obfuscated.java:"); + Console.printInt(callSiteId); Console.printString(")\n"); + } else { + CallSite callSite = findCallSiteById(callSiteId, stackFrame); + CallSiteLocation location = callSite.location; + while (location != null) { + MethodLocation methodLocation = location != null ? location.method : null; - location = location.next; + Console.printString(" at "); + if (methodLocation.className == null || methodLocation.methodName == null) { + Console.printString("(Unknown method)"); + } else { + Console.printString(methodLocation.className.value); + Console.printString("."); + Console.printString(methodLocation.methodName.value); + } + Console.printString("("); + if (methodLocation.fileName != null && location.lineNumber >= 0) { + Console.printString(methodLocation.fileName.value); + Console.printString(":"); + Console.printInt(location.lineNumber); + } + Console.printString(")\n"); + + location = location.next; + } } stackFrame = ShadowStack.getNextStackFrame(stackFrame); } @@ -137,14 +146,14 @@ public final class ExceptionHandling { } @Unmanaged - public static int callStackSize() { + private static int callStackSize() { Address stackFrame = ShadowStack.getStackTop(); int size = 0; while (stackFrame != null) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSiteLocation location = callSite.location; - if (location == null) { + if (isObfuscated() || location == null) { size++; } else { while (location != null) { @@ -159,27 +168,35 @@ public final class ExceptionHandling { } @Unmanaged - public static void fillStackTrace(StackTraceElement[] target) { + public static StackTraceElement[] fillStackTrace() { Address stackFrame = ShadowStack.getStackTop(); + int size = callStackSize(); + + ShadowStack.allocStack(1); + StackTraceElement[] target = new StackTraceElement[size]; + ShadowStack.registerGCRoot(0, target); + int index = 0; while (stackFrame != null) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSiteLocation location = callSite.location; - if (location == null) { - target[index++] = createElement("", "", null, location.lineNumber); + if (isObfuscated()) { + target[index++] = new StackTraceElement("Obfuscated", "obfuscated", "Obfuscated.java", callSiteId); + } else if (location == null) { + target[index++] = new StackTraceElement("", "", null, location.lineNumber); } else { while (location != null) { MethodLocation methodLocation = location.method; StackTraceElement element; if (methodLocation != null) { - element = createElement( + element = new StackTraceElement( methodLocation.className != null ? methodLocation.className.value : "", methodLocation.methodName != null ? methodLocation.methodName.value : "", methodLocation.fileName != null ? methodLocation.fileName.value : null, location.lineNumber); } else { - element = createElement("", "", null, location.lineNumber); + element = new StackTraceElement("", "", null, location.lineNumber); } target[index++] = element; location = location.next; @@ -187,10 +204,8 @@ public final class ExceptionHandling { } stackFrame = ShadowStack.getNextStackFrame(stackFrame); } - } + ShadowStack.releaseStack(1); - private static StackTraceElement createElement(String className, String methodName, String fileName, - int lineNumber) { - return new StackTraceElement(className, methodName, fileName, lineNumber); + return target; } } diff --git a/core/src/main/resources/org/teavm/backend/c/definitions.h b/core/src/main/resources/org/teavm/backend/c/definitions.h index c25d12577..ba20907e5 100644 --- a/core/src/main/resources/org/teavm/backend/c/definitions.h +++ b/core/src/main/resources/org/teavm/backend/c/definitions.h @@ -57,4 +57,8 @@ #ifndef TEAVM_GC_LOG #define TEAVM_GC_LOG 0 +#endif + +#ifndef TEAVM_OBFUSCATED + #define TEAVM_OBFUSCATED 0 #endif \ No newline at end of file diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index f89d0d250..6e9cdc3ca 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -342,6 +342,7 @@ public class TeaVMTool { cTarget.setLineNumbersGenerated(debugInformationGenerated); cTarget.setLongjmpUsed(longjmpSupported); cTarget.setHeapDump(heapDump); + cTarget.setObfuscated(minifying); return cTarget; }