/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.ant.taskdefs;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.tools.ant.BuildException;
import org.jnode.vm.facade.VmUtils;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.attrs.Attributes;
/**
* That ant task will check that native methods are properly implemented
* for JNode.
*
* @author Fabien DUMINY (fduminy at jnode.org)
*/
public class NativeCheckTask extends FileSetTask {
/**
* potential implementation of the native methods for JNode.
* key: className (String)
* value: native methods for the class (List<NativeMethod>)
*/
private Map<String, List<NativeMethod>> jnodeNativeMethods = new HashMap<String, List<NativeMethod>>();
/**
* native methods per class.
* key: className (String)
* value: native methods for the class (List<NativeMethod>)
*/
private Map<String, List<NativeMethod>> nativeMethods = new HashMap<String, List<NativeMethod>>();
private Set<String> missingClasses = new TreeSet<String>();
private Map<String, List<NativeMethod>> missingMethods = new HashMap<String, List<NativeMethod>>();
protected void doExecute() throws BuildException {
// process all classes to find native methods
// and classes that could potentially implement native methods
// for JNode
processFiles();
// now, check all native methods are properly implemented
// for JNode
int nbNativeMethods = 0;
for (String className : nativeMethods.keySet()) {
List<NativeMethod> methods = nativeMethods.get(className);
for (NativeMethod method : methods) {
nbNativeMethods++;
checkNativeMethod(className, method);
}
}
// report
final String INDENT = " ";
int nbMissingClasses = missingClasses.size();
if (!missingClasses.isEmpty()) {
System.err.println("Missing classes:");
for (String missingClass : missingClasses) {
System.err.println(INDENT + missingClass);
}
}
int nbMissingMethods = 0;
if (!missingMethods.isEmpty()) {
System.err.println("Missing methods:");
for (String cls : missingMethods.keySet()) {
System.err.println(INDENT + " class " + cls);
for (NativeMethod m : missingMethods.get(cls)) {
System.err.println(INDENT + INDENT + m.getName());
nbMissingMethods++;
}
}
}
System.out.println("Found " + nbNativeMethods + " native methods in " + nativeMethods.size() + " classes");
if ((nbMissingMethods != 0) || (nbMissingClasses != 0)) {
System.err.println(missingClasses.size() + " missing classes. " + nbMissingMethods + " missing methods");
String message = "Some native methods are not properly implemented (see errors above)";
if (failOnError) {
throw new BuildException(message);
} else {
System.err.println("[FAILED] " + message);
}
} else {
System.out.println("[OK] All native methods are properly defined");
}
}
private boolean checkNativeMethod(String className, NativeMethod method) {
boolean hasError = false;
String jnodeNativeClass = VmUtils.getNativeClassName(className.replace('/', '.'));
if (jnodeNativeMethods.containsKey(jnodeNativeClass)) {
List<NativeMethod> methods = jnodeNativeMethods.get(jnodeNativeClass);
boolean found = false;
for (NativeMethod nvMethod : methods) {
if (method.getName().equals(nvMethod.getName())) {
found = true;
break;
}
}
if (!found) {
List<NativeMethod> methodList = missingMethods.get(jnodeNativeClass);
if (methodList == null) {
methodList = new ArrayList<NativeMethod>();
missingMethods.put(jnodeNativeClass, methodList);
}
methodList.add(method);
hasError = true;
}
} else {
missingClasses.add(jnodeNativeClass);
hasError = true;
}
return hasError;
}
@Override
protected void processFile(File file) throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
NativeMethodClassVisitor v = getNativeMethods(file, fis);
if ((v != null) && !v.getNativeMethods().isEmpty()) {
if (v.couldImplementNativeMethods()) {
jnodeNativeMethods.put(v.getClassName(), v.getNativeMethods());
} else {
nativeMethods.put(v.getClassName(), v.getNativeMethods());
}
}
} finally {
if (fis != null) {
fis.close();
}
}
}
private NativeMethodClassVisitor getNativeMethods(File file, InputStream inputClass) throws BuildException {
ClassWriter cw = new ClassWriter(false);
NativeMethodClassVisitor v = null;
try {
ClassReader cr = new ClassReader(inputClass);
v = new NativeMethodClassVisitor(file, cw);
cr.accept(v, Attributes.getDefaultAttributes(), true);
if (v.allowNatives()) {
// if natives are allowed for that class, no need to check
// for a pure java implementation
v = null;
}
} catch (Exception ex) {
System.err.println("Unable to load class in file " + file.getAbsolutePath() + " : " + ex.getMessage());
}
return v;
}
private static class NativeMethodClassVisitor extends ClassAdapter {
private String className;
private boolean couldImplementNativeMethods;
private boolean allowNatives = false;
private List<NativeMethod> nativeMethods = new ArrayList<NativeMethod>();
public NativeMethodClassVisitor(File file, ClassVisitor cv) {
super(cv);
}
@Override
public void visit(int version,
int access,
String name,
String superName,
String[] interfaces,
String sourceFile) {
this.className = name.replace('/', '.');
this.allowNatives = VmUtils.allowNatives(className, "x86"); //TODO hard coded architecture: change that !
this.couldImplementNativeMethods = VmUtils.couldImplementNativeMethods(className);
super.visit(version, access, name, superName, interfaces, sourceFile);
}
@Override
public CodeVisitor visitMethod(int access,
String name,
String desc,
String[] exceptions,
Attribute attrs) {
if (!allowNatives) {
// we don't allow native for that class =>
// we must have a pure java implementation for that method
if ((couldImplementNativeMethods && isStatic(access)) ||
isNative(access)) {
nativeMethods.add(new NativeMethod(access, name, desc));
}
}
return null; // we don't to visit inside the method
}
public String getClassName() {
return className;
}
public List<NativeMethod> getNativeMethods() {
return nativeMethods;
}
public boolean couldImplementNativeMethods() {
return couldImplementNativeMethods;
}
public boolean allowNatives() {
return allowNatives;
}
}
private static class NativeMethod {
private final int access;
private final String name;
private final String desc;
public NativeMethod(int access, String name, String desc) {
super();
this.access = access;
this.name = name;
this.desc = desc;
}
public int getAccess() {
return access;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
public String toString() {
return name + " " + desc;
}
}
public static boolean isNative(int access) {
return isSet(access, org.objectweb.asm.Constants.ACC_NATIVE);
}
public static boolean isStatic(int access) {
return isSet(access, org.objectweb.asm.Constants.ACC_STATIC);
}
private static boolean isSet(int access, int flag) {
return ((access & flag) == flag);
}
}