Skip to content

Commit

Permalink
Fix corner case in server-side Stack trace debofuscation.
Browse files Browse the repository at this point in the history
Some fields and methods have special hardcoded obfuscated names, e. g.
Object::getClass() -> gC, Object::__clazz -> cZ, etc. Those names
were not prevented to clash with obfuscated method names, creating
ambiguous symbols in the Symbol Map. If a method that had one of
those ambiguous symbols appeard in the stack, it would have been
deobfuscated incorrectly.

This patch fixes by only loading symbols for methods not fields, and
removes all special handling for java.lang.Object fields and methods.

Change-Id: Id4b1c8ef9d4e1ec4bc96db2a04ed2cd8ab377a71
  • Loading branch information
rluble authored and Gerrit Code Review committed Jul 23, 2015
1 parent 541858e commit 9f283b7
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 104 deletions.
Expand Up @@ -23,7 +23,6 @@
import com.google.gwt.dev.cfg.PermutationProperties; import com.google.gwt.dev.cfg.PermutationProperties;
import com.google.gwt.dev.jjs.HasSourceInfo; import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.JsOutputOption;
import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.Context;
Expand Down Expand Up @@ -436,10 +435,7 @@ public void endVisit(JField x, Context ctx) {
recordSymbol(x, jsName); recordSymbol(x, jsName);
} else { } else {
JsName jsName; JsName jsName;
if (specialObfuscatedFields.containsKey(x)) { if (x.isJsTypeMember()) {
jsName = scopeStack.peek().declareName(mangleNameSpecialObfuscate(x));
jsName.setObfuscatable(false);
} else if (x.isJsTypeMember()) {
jsName = scopeStack.peek().declareName(name, name); jsName = scopeStack.peek().declareName(name, name);
jsName.setObfuscatable(false); jsName.setObfuscatable(false);
} else { } else {
Expand Down Expand Up @@ -567,10 +563,6 @@ public boolean visit(JMethod x, Context ctx) {
// so that it can be referred when generating the vtable of a subclass that // so that it can be referred when generating the vtable of a subclass that
// increases the visibility of this method. // increases the visibility of this method.
polymorphicNames.put(typeOracle.getTopMostDefinition(x), polyName); polymorphicNames.put(typeOracle.getTopMostDefinition(x), polyName);
} else if (specialObfuscatedMethodSigs.containsKey(x.getSignature())) {
polyName = interfaceScope.declareName(mangleNameSpecialObfuscate(x));
polyName.setObfuscatable(false);
// if a JsType and we can set set the interface method to non-obfuscatable
} else if (x.isOrOverridesJsTypeMethod() && !typeOracle.needsJsInteropBridgeMethod(x)) { } else if (x.isOrOverridesJsTypeMethod() && !typeOracle.needsJsInteropBridgeMethod(x)) {
if (x.isOrOverridesJsProperty()) { if (x.isOrOverridesJsProperty()) {
// Prevent JsProperty functions like x() from colliding with intended JS native // Prevent JsProperty functions like x() from colliding with intended JS native
Expand Down Expand Up @@ -3390,8 +3382,6 @@ public static Pair<JavaToJavaScriptMap, Set<JsNode>> exec(TreeLogger logger, JPr
*/ */
private Set<HasName> nameOfTargets = Sets.newHashSet(); private Set<HasName> nameOfTargets = Sets.newHashSet();


private final JsOutputOption output;

private final boolean optimize; private final boolean optimize;


private final TreeLogger logger; private final TreeLogger logger;
Expand All @@ -3400,16 +3390,6 @@ public static Pair<JavaToJavaScriptMap, Set<JsNode>> exec(TreeLogger logger, JPr
// TODO(rluble) move optimizations to a Java AST optimization pass. // TODO(rluble) move optimizations to a Java AST optimization pass.
private final boolean incremental; private final boolean incremental;


/**
* All of the fields in String and Array need special handling for interop.
*/
private final Map<JField, String> specialObfuscatedFields = Maps.newHashMap();

/**
* All of the methods in String and Array need special handling for interop.
*/
private final Map<String, String> specialObfuscatedMethodSigs = Maps.newHashMap();

/** /**
* If true, polymorphic functions are made anonymous vtable declarations and * If true, polymorphic functions are made anonymous vtable declarations and
* not assigned topScope identifiers. * not assigned topScope identifiers.
Expand Down Expand Up @@ -3458,53 +3438,13 @@ private GenerateJavaScriptAST(TreeLogger logger, JProgram program, JsProgram jsP
this.properties = properties; this.properties = properties;


PrecompileTaskOptions options = compilerContext.getOptions(); PrecompileTaskOptions options = compilerContext.getOptions();
this.output = options.getOutput();
this.optimize = options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT; this.optimize = options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT;
this.methodNameMappingMode = options.getMethodNameDisplayMode(); this.methodNameMappingMode = options.getMethodNameDisplayMode();
assert methodNameMappingMode != null; assert methodNameMappingMode != null;
this.incremental = options.isIncrementalCompileEnabled(); this.incremental = options.isIncrementalCompileEnabled();


this.stripStack = JsStackEmulator.getStackMode(properties) == JsStackEmulator.StackMode.STRIP; this.stripStack = JsStackEmulator.getStackMode(properties) == JsStackEmulator.StackMode.STRIP;
this.closureCompilerFormatEnabled = options.isClosureCompilerFormatEnabled(); this.closureCompilerFormatEnabled = options.isClosureCompilerFormatEnabled();

/*
* Because we modify the JavaScript String prototype, all fields and
* polymorphic methods on String and super types need special handling.
*/

// Object polymorphic
Map<String, String> namesToIdents = Maps.newHashMap();
namesToIdents.put("getClass", "gC");
namesToIdents.put("hashCode", "hC");
namesToIdents.put("equals", "eQ");
namesToIdents.put("toString", "tS");
namesToIdents.put("finalize", "fZ");

List<JMethod> methods = Lists.newArrayList(program.getTypeJavaLangObject().getMethods());
for (JMethod method : methods) {
if (method.canBePolymorphic()) {
String ident = namesToIdents.get(method.getName());
assert ident != null : method.getEnclosingType().getName() + "::" + method.getName() +
" is not in the list of known methods.";
specialObfuscatedMethodSigs.put(method.getSignature(), ident);
}
}

namesToIdents.clear();
// Object fields
namesToIdents.put("expando", "eX");
namesToIdents.put("typeMarker", "tM");
namesToIdents.put("castableTypeMap", "cM");
namesToIdents.put("___clazz", "cZ");

for (JField field : program.getTypeJavaLangObject().getFields()) {
if (!field.isStatic()) {
String ident = namesToIdents.get(field.getName());
assert ident != null : field.getEnclosingType().getName() + "::" + field.getName() +
" is not in the list of known fields.";
specialObfuscatedFields.put(field, ident);
}
}
} }


/** /**
Expand Down Expand Up @@ -3581,32 +3521,6 @@ String mangleNameForJsProperty(JMethod x) {
return StringInterner.get().intern(JjsUtils.constructManglingSignature(x, mangledName)); return StringInterner.get().intern(JjsUtils.constructManglingSignature(x, mangledName));
} }


String mangleNameSpecialObfuscate(JField x) {
assert (specialObfuscatedFields.containsKey(x));
switch (output) {
case OBFUSCATED:
return specialObfuscatedFields.get(x);
case PRETTY:
return x.getName() + "$";
case DETAILED:
return mangleName(x) + "$";
}
throw new InternalCompilerException("Unknown output mode");
}

String mangleNameSpecialObfuscate(JMethod x) {
assert (specialObfuscatedMethodSigs.containsKey(x.getSignature()));
switch (output) {
case OBFUSCATED:
return specialObfuscatedMethodSigs.get(x.getSignature());
case PRETTY:
return x.getName() + "$";
case DETAILED:
return mangleNameForPoly(x) + "$";
}
throw new InternalCompilerException("Unknown output mode");
}

private final Map<JType, JDeclarationStatement> classLiteralDeclarationsByType = private final Map<JType, JDeclarationStatement> classLiteralDeclarationsByType =
Maps.newLinkedHashMap(); Maps.newLinkedHashMap();


Expand Down
55 changes: 39 additions & 16 deletions dev/core/test/com/google/gwt/core/ext/linker/SymbolMapTest.java
Expand Up @@ -22,8 +22,10 @@
import com.google.gwt.dev.util.arg.OptionOptimize; import com.google.gwt.dev.util.arg.OptionOptimize;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger; import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Function; import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Iterables; import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.util.tools.Utility; import com.google.gwt.util.tools.Utility;


import junit.framework.TestCase; import junit.framework.TestCase;
Expand All @@ -34,6 +36,7 @@
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator;
import java.util.Map; import java.util.Map;


/** /**
Expand Down Expand Up @@ -217,32 +220,52 @@ private void assertSymbolMapSanity(int optimizeLevel) throws IOException,
new com.google.gwt.dev.Compiler(options).run(logger); new com.google.gwt.dev.Compiler(options).run(logger);
// Change parentDir for cached/pre-built reports // Change parentDir for cached/pre-built reports
String parentDir = options.getExtraDir() + "/" + benchmark; String parentDir = options.getExtraDir() + "/" + benchmark;
for (Map<String, SimpleSymbolData> symbolDataBySymbolName : for (Map<String, SimpleSymbolData> symbolDataByJsniIdentifier :
loadSymbolMaps(new File(parentDir + "/symbolMaps/"))) { loadSymbolMaps(new File(parentDir + "/symbolMaps/"))) {
assertTrue(!symbolDataBySymbolName.isEmpty()); assertTrue(!symbolDataByJsniIdentifier.isEmpty());
assertNotNull(symbolDataBySymbolName.get(JSE_METHOD)); assertNotNull(symbolDataByJsniIdentifier.get(JSE_METHOD));
assertTrue(symbolDataBySymbolName.get(JSE_METHOD).isMethod()); assertTrue(symbolDataByJsniIdentifier.get(JSE_METHOD).isMethod());
assertFalse(symbolDataBySymbolName.get(JSE_METHOD).isField()); assertFalse(symbolDataByJsniIdentifier.get(JSE_METHOD).isField());
assertFalse(symbolDataBySymbolName.get(JSE_METHOD).isClass()); assertFalse(symbolDataByJsniIdentifier.get(JSE_METHOD).isClass());
assertNotNull(symbolDataBySymbolName.get(JSE_FIELD)); assertNotNull(symbolDataByJsniIdentifier.get(JSE_FIELD));
assertTrue(symbolDataBySymbolName.get(JSE_FIELD).isField()); assertTrue(symbolDataByJsniIdentifier.get(JSE_FIELD).isField());
assertFalse(symbolDataBySymbolName.get(JSE_FIELD).isMethod()); assertFalse(symbolDataByJsniIdentifier.get(JSE_FIELD).isMethod());
assertFalse(symbolDataBySymbolName.get(JSE_FIELD).isClass()); assertFalse(symbolDataByJsniIdentifier.get(JSE_FIELD).isClass());
assertNotNull(symbolDataBySymbolName.get(JSE_CLASS)); assertNotNull(symbolDataByJsniIdentifier.get(JSE_CLASS));
assertTrue(symbolDataBySymbolName.get(JSE_CLASS).isClass()); assertTrue(symbolDataByJsniIdentifier.get(JSE_CLASS).isClass());
assertFalse(symbolDataBySymbolName.get(JSE_CLASS).isField()); assertFalse(symbolDataByJsniIdentifier.get(JSE_CLASS).isField());
assertFalse(symbolDataBySymbolName.get(JSE_CLASS).isMethod()); assertFalse(symbolDataByJsniIdentifier.get(JSE_CLASS).isMethod());
if (optimizeLevel == OptionOptimize.OPTIMIZE_LEVEL_DRAFT) { if (optimizeLevel == OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
assertNotNull(symbolDataBySymbolName.get(UNINSTANTIABLE_CLASS)); assertNotNull(symbolDataByJsniIdentifier.get(UNINSTANTIABLE_CLASS));
} else { } else {
assertNull(symbolDataBySymbolName.get(UNINSTANTIABLE_CLASS)); assertNull(symbolDataByJsniIdentifier.get(UNINSTANTIABLE_CLASS));
} }
assertSymbolUniquenessForMethods(symbolDataByJsniIdentifier);
} }
} finally { } finally {
Util.recursiveDelete(work, false); Util.recursiveDelete(work, false);
} }
} }


private void assertSymbolUniquenessForMethods(
Map<String, SimpleSymbolData> symbolDataByJsniIdentifier) {
Multimap<String, SymbolData> methodSymbolDataBySymbol = HashMultimap.create();
for (SymbolData symbolData : symbolDataByJsniIdentifier.values()) {
if (symbolData.isMethod()) {
methodSymbolDataBySymbol.put(symbolData.getSymbolName(), symbolData);
}
}
Iterator<String> iterator = methodSymbolDataBySymbol.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if (methodSymbolDataBySymbol.get(key).size() <= 1) {
iterator.remove();
}
}
assertTrue("The following method symbols where not unique " + methodSymbolDataBySymbol,
methodSymbolDataBySymbol.isEmpty());
}

public void testSymbolMapSanityDraft() throws Exception { public void testSymbolMapSanityDraft() throws Exception {
assertSymbolMapSanity(OptionOptimize.OPTIMIZE_LEVEL_DRAFT); assertSymbolMapSanity(OptionOptimize.OPTIMIZE_LEVEL_DRAFT);
} }
Expand Down
Expand Up @@ -172,7 +172,7 @@ public void testThrowWithInlineMethodCall() throws Exception {
"var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" +
"$location[stackIndex]='EntryPoint.java:'+'6',$clinit_EntryPoint();" + "$location[stackIndex]='EntryPoint.java:'+'6',$clinit_EntryPoint();" +
"throw new RuntimeException(" + "throw new RuntimeException(" +
"($tmp=($location[stackIndex]='EntryPoint.java:'+'4',thing).toString$()," + "($tmp=($location[stackIndex]='EntryPoint.java:'+'4',thing).toString()," +
"$location[stackIndex]='EntryPoint.java:'+'7',$tmp))" + "$location[stackIndex]='EntryPoint.java:'+'7',$tmp))" +
"}"); "}");
} }
Expand Down
Expand Up @@ -400,6 +400,13 @@ private Map<String, String> loadSymbolMap(
int idx = line.indexOf(','); int idx = line.indexOf(',');
String symbol = line.substring(0, idx); String symbol = line.substring(0, idx);
String symbolData = line.substring(idx + 1); String symbolData = line.substring(idx + 1);

// Is it a method symbol?
if (!symbolData.substring(0, symbolData.indexOf(",")).contains(")")) {
// Methods jsni names have to contain parens.
continue;
}

if (requiredSymbols.contains(symbol) || !lazyLoad) { if (requiredSymbols.contains(symbol) || !lazyLoad) {
symbolsLeftToFind.remove(symbol); symbolsLeftToFind.remove(symbol);
toReturn.put(symbol, symbolData); toReturn.put(symbol, symbolData);
Expand Down

0 comments on commit 9f283b7

Please sign in to comment.