Skip to content

Commit

Permalink
Now we use ASM for bytecode engineering to resolve constants as we want.
Browse files Browse the repository at this point in the history
Also fixed double precision issue.
  • Loading branch information
Atsushi Eno committed Aug 19, 2011
1 parent ec700d3 commit 7b0f30f
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 35 deletions.
6 changes: 5 additions & 1 deletion .gitignore
@@ -1,2 +1,6 @@
*.jar
jar2xml.jar
obj
tmpout/*.xml
annotations/*.xml
scraper.exe
scraper.exe.mdb
14 changes: 12 additions & 2 deletions JavaArchive.java
Expand Up @@ -25,6 +25,7 @@
package jar2xml;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
Expand All @@ -34,6 +35,8 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;

public class JavaArchive {

Expand Down Expand Up @@ -61,14 +64,21 @@ public List<JavaPackage> getPackages ()
if (name.endsWith (".class")) {
name = name.substring (0, name.length () - 6);
try {
InputStream stream = file.getInputStream (entry);
ClassReader reader = new ClassReader (stream);
ClassNode node = new ClassNode ();
reader.accept (node, 0);

Class c = loader.loadClass (name.replace ('/', '.'));
String pkgname = c.getPackage ().getName ();
//String pkgname = c.getPackage ().getName ();
String pkgname = name.substring (0, name.lastIndexOf ('/')).replace ('/', '.');

JavaPackage pkg = packages.get (pkgname);
if (pkg == null) {
pkg = new JavaPackage (pkgname);
packages.put (pkgname, pkg);
}
pkg.addClass (new JavaClass (c));
pkg.addClass (new JavaClass (c, node));
} catch (Throwable t) {
System.err.println ("Couldn't load class " + name);
}
Expand Down
55 changes: 41 additions & 14 deletions JavaClass.java
Expand Up @@ -42,18 +42,26 @@
import java.util.regex.Matcher;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;

public class JavaClass implements Comparable<JavaClass> {

private Class jclass;
private ClassNode asm;
private Map<String,FieldNode> asmFields;
private List<String> deprecatedFields;
private List<String> deprecatedMethods;

public JavaClass (Class jclass)
public JavaClass (Class jclass, ClassNode asm)
{
this.jclass = jclass;
deprecatedFields = AndroidDocScraper.getDeprecatedFields (jclass);
deprecatedMethods = AndroidDocScraper.getDeprecatedMethods (jclass);
asmFields = new HashMap<String,FieldNode> ();

for (FieldNode fn : (List<FieldNode>) asm.fields)
asmFields.put (fn.name, fn);
}

public int compareTo (JavaClass jc)
Expand Down Expand Up @@ -134,7 +142,7 @@ int getConstructorParameterOffset (Constructor ctor)
return 0;
}

void appendField (Field field, Document doc, Element parent)
void appendField (Field field, FieldNode asmField, Document doc, Element parent)
{
int mods = field.getModifiers ();
if (!Modifier.isPublic (mods) && !Modifier.isProtected (mods))
Expand All @@ -153,24 +161,40 @@ void appendField (Field field, Document doc, Element parent)
e.setAttribute ("visibility", Modifier.isPublic (mods) ? "public" : "protected");
e.setAttribute ("volatile", Modifier.isVolatile (mods) ? "true" : "false");
setDeprecatedAttr (e, field.getDeclaredAnnotations (), e.getAttribute ("name"));
if (Modifier.isStatic (mods) && Modifier.isFinal (mods) && Modifier.isPublic (mods)) {

// *** constant value retrieval ***
// sadly, there is no perfect solution:
// - basically we want to use ASM, but sometimes ASM fails
// to create FieldNode instance.
// - on the other hand, reflection
// - does not allow access to protected fields.
// - sometimes returns "default" value for "undefined"
// values such as 0 for ints and false for boolean.
//
// basically we use ASM here.

if (asmField == null)
// this happens to couple of fields on java.awt.font.TextAttribute, java.lang.Double/Float and so on.
System.err.println ("!!!!! WARNING!!! null ASM FieldNode for " + field);
else if (asmField.value != null) {
String type = e.getAttribute ("type");
boolean isPublic = Modifier.isPublic (mods);
try {
if (type == "int")
e.setAttribute ("value", String.format ("%d", field.getInt (null)));
e.setAttribute ("value", String.format ("%d", asmField.value));
else if (type == "byte")
e.setAttribute ("value", String.format ("%d", field.getByte (null)));
e.setAttribute ("value", String.format ("%d", asmField.value));
else if (type == "char")
e.setAttribute ("value", String.format ("%d", (int) field.getChar (null)));
e.setAttribute ("value", String.format ("%d", asmField.value));
else if (type == "short")
e.setAttribute ("value", String.format ("%d", field.getShort (null)));
e.setAttribute ("value", String.format ("%d", asmField.value));
else if (type == "long")
e.setAttribute ("value", String.format ("%dL", field.getLong (null)));
e.setAttribute ("value", String.format ("%dL", asmField.value));
else if (type == "float")
e.setAttribute ("value", String.format ("%f", field.getFloat (null)));
e.setAttribute ("value", String.format ("%f", isPublic ? field.getFloat (null) : asmField.value));
else if (type == "double") {
// see java.lang.Double constants.
double dvalue = field.getDouble (null);
double dvalue = (Double) asmField.value;
String svalue;

if (dvalue == Double.MAX_VALUE)
Expand All @@ -184,13 +208,16 @@ else if (dvalue == Double.POSITIVE_INFINITY)
else if (dvalue == Double.NEGATIVE_INFINITY)
svalue = "(-1.0 / 0.0)";
else
svalue = String.format ("%f", dvalue);
// FIXME: here we specify "limited" digits for formatting.
// This should fix most cases, but this could still result in not-precise value.
// Math.E and Math.PI works with this.
svalue = String.format ("%.15f", dvalue);
e.setAttribute ("value", svalue);
}
else if (type == "boolean")
e.setAttribute ("value", field.getBoolean (null) ? "true" : "false");
e.setAttribute ("value", 0 == (Integer) asmField.value ? "true" : "false");
else if (type == "java.lang.String") {
String value = (String) field.get (null);
String value = (String) asmField.value;
if (value != null)
e.setAttribute ("value", "\"" + value.replace ("\\", "\\\\") + "\"");
}
Expand Down Expand Up @@ -467,7 +494,7 @@ else if (hret != null && !mret.isAssignableFrom (hret)) {
Field [] fields = jclass.getDeclaredFields ();
sortFields (fields);
for (Field field : fields)
appendField (field, doc, e);
appendField (field, asmFields.get (field.getName ()), doc, e);
}
parent.appendChild (e);
}
Expand Down
4 changes: 4 additions & 0 deletions MANIFEST.MF
@@ -0,0 +1,4 @@
Manifest-Version: 1.0
Main-Class: jar2xml.Start
Class-Path: asm-debug-all-3.3.1.jar

6 changes: 3 additions & 3 deletions Makefile
Expand Up @@ -17,11 +17,11 @@ sources = \
JavaPackage.java \
Start.java

$(TARGET): $(sources)
$(TARGET): $(sources) MANIFEST.MF
-rm -rf obj
mkdir -p obj
javac -g -d obj $(sources)
jar cfe "$@" jar2xml.Start -C obj/ .
javac -g -d obj $(sources) -cp asm-debug-all-4.0_RC1.jar
jar cfm "$@" MANIFEST.MF asm-debug-all-4.0_RC1.jar -C obj/ .

scraper.exe : scraper.cs
mcs -debug scraper.cs
Expand Down
36 changes: 21 additions & 15 deletions README
Expand Up @@ -8,6 +8,7 @@ and associated documentation. The information is stored in XML format.
- xmllint (for doc scraping)
- xmlstarlet (ditto)

Also it uses ASM (included in the sources)

* Tools

Expand Down Expand Up @@ -77,21 +78,6 @@ and associated documentation. The information is stored in XML format.
excluded, but this brings another check to *not* exclude methods when
the corresponding base methods are excluded).

** Byte code engineering

- Some fields seem to be incorrectly marked as constant. For example,
android.os.Build.TIME is not a constant, but since the field.getLong()
returns 0, it is set as 0 (and it does not return null for get()).

java.io.File.pathSeparatorChar has the same problem, but it is worse;
it returns non-zero value so we cannot depend on the value.

Solution ideas: scrape constant values, or use ASM.

- Constant values for "protected" fields need to be retrieved, but Java reflection API throws IllegalAccessException for such attempt.

This likely has to be resolved by bytecode engineering (such as ASM).

** Method override resolution

- Do not skip certain overriden methods. This in JavaClass.java gives
Expand Down Expand Up @@ -233,3 +219,23 @@ Now we have version-aware scraper and resolved the issue described below.
A possible approach is to checkout API docs for *every* version (from
AOSP).
</historical>


** Byte code engineering

Now we use ASM to solve the issues below:

<historical>
- Some fields were incorrectly marked as constant. For example,
android.os.Build.TIME is not a constant, but since the field.getLong()
returns 0, it is set as 0 (and it does not return null for get()).

java.io.File.pathSeparatorChar has the same problem, but it is worse;
it returns non-zero value so we cannot depend on the value.

Solution ideas: scrape constant values, or use ASM.

- Constant values for "protected" fields need to be retrieved, but Java reflection API throws IllegalAccessException for such attempt.

This likely has to be resolved by bytecode engineering (such as ASM).
</historical>
Binary file added asm-debug-all-4.0_RC1.jar
Binary file not shown.

0 comments on commit 7b0f30f

Please sign in to comment.