Bug 1086693 - Part 5: Add a 'catchException' mode to JNI generator. r=ckitching, a=lsblakk
authorJames Willcox <snorp@snorp.net>
Thu, 13 Nov 2014 12:47:24 -0600
changeset 235258 f3ce57da55ad90d3e19da5ec81d7f1c73498430f
parent 235257 6b3f36796e694263d937f0518f043e03373ad801
child 235259 f28a8076587daef8786f297aaf66eed27905eb62
push id611
push userraliiev@mozilla.com
push dateMon, 05 Jan 2015 23:23:16 +0000
treeherdermozilla-release@345cd3b9c445 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckitching, lsblakk
bugs1086693
milestone35.0a2
Bug 1086693 - Part 5: Add a 'catchException' mode to JNI generator. r=ckitching, a=lsblakk
build/annotationProcessors/AnnotationInfo.java
build/annotationProcessors/CodeGenerator.java
build/annotationProcessors/utils/GeneratableElementIterator.java
build/annotationProcessors/utils/Utils.java
mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
--- a/build/annotationProcessors/AnnotationInfo.java
+++ b/build/annotationProcessors/AnnotationInfo.java
@@ -7,17 +7,24 @@ package org.mozilla.gecko.annotationProc
 /**
  * Object holding annotation data. Used by GeneratableElementIterator.
  */
 public class AnnotationInfo {
     public final String wrapperName;
     public final boolean isMultithreaded;
     public final boolean noThrow;
     public final boolean narrowChars;
+    public final boolean catchException;
 
     public AnnotationInfo(String aWrapperName, boolean aIsMultithreaded,
-                          boolean aNoThrow, boolean aNarrowChars) {
+                          boolean aNoThrow, boolean aNarrowChars, boolean aCatchException) {
         wrapperName = aWrapperName;
         isMultithreaded = aIsMultithreaded;
         noThrow = aNoThrow;
         narrowChars = aNarrowChars;
+        catchException = aCatchException;
+
+        if (!noThrow && catchException) {
+            // It doesn't make sense to have these together
+            throw new IllegalArgumentException("noThrow and catchException are not allowed together");
+        }
     }
 }
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -106,27 +106,30 @@ public class CodeGenerator {
         generateMemberCommon(theMethod, CMethodName, mClassToWrap);
 
         boolean isFieldStatic = Utils.isMemberStatic(theMethod);
 
         Class<?>[] parameterTypes = theMethod.getParameterTypes();
         Class<?> returnType = theMethod.getReturnType();
 
         // Get the C++ method signature for this method.
-        String implementationSignature = Utils.getCImplementationMethodSignature(parameterTypes, returnType, CMethodName, mCClassName, aMethodTuple.mAnnotationInfo.narrowChars);
-        String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType, CMethodName, mCClassName, isFieldStatic, aMethodTuple.mAnnotationInfo.narrowChars);
+        String implementationSignature = Utils.getCImplementationMethodSignature(parameterTypes, returnType, CMethodName,
+            mCClassName, aMethodTuple.mAnnotationInfo.narrowChars, aMethodTuple.mAnnotationInfo.catchException);
+        String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType,
+            CMethodName, mCClassName, isFieldStatic, aMethodTuple.mAnnotationInfo.narrowChars, aMethodTuple.mAnnotationInfo.catchException);
 
         // Add the header signature to the header file.
         writeSignatureToHeader(headerSignature);
 
         // Use the implementation signature to generate the method body...
         writeMethodBody(implementationSignature, theMethod, mClassToWrap,
                         aMethodTuple.mAnnotationInfo.isMultithreaded,
                         aMethodTuple.mAnnotationInfo.noThrow,
-                        aMethodTuple.mAnnotationInfo.narrowChars);
+                        aMethodTuple.mAnnotationInfo.narrowChars,
+                        aMethodTuple.mAnnotationInfo.catchException);
     }
 
     private void generateGetterOrSetterBody(Field aField, String aFieldName, boolean aIsFieldStatic, boolean isSetter, boolean aNarrowChars) {
         StringBuilder argumentContent = null;
         Class<?> fieldType = aField.getType();
 
         if (isSetter) {
             Class<?>[] setterArguments = new Class<?>[]{fieldType};
@@ -191,79 +194,82 @@ public class CodeGenerator {
         Class<?> fieldType = theField.getType();
 
         generateMemberCommon(theField, CFieldName, mClassToWrap);
 
         boolean isFieldStatic = Utils.isMemberStatic(theField);
         boolean isFieldFinal = Utils.isMemberFinal(theField);
 
         String getterName = "get" + CFieldName;
-        String getterSignature = Utils.getCImplementationMethodSignature(EMPTY_CLASS_ARRAY, fieldType, getterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars);
-        String getterHeaderSignature = Utils.getCHeaderMethodSignature(EMPTY_CLASS_ARRAY, GETTER_ARGUMENT_ANNOTATIONS, fieldType, getterName, mCClassName, isFieldStatic, aFieldTuple.mAnnotationInfo.narrowChars);
+        String getterSignature = Utils.getCImplementationMethodSignature(EMPTY_CLASS_ARRAY, fieldType, getterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars, false);
+        String getterHeaderSignature = Utils.getCHeaderMethodSignature(EMPTY_CLASS_ARRAY, GETTER_ARGUMENT_ANNOTATIONS, fieldType, getterName, mCClassName, isFieldStatic, aFieldTuple.mAnnotationInfo.narrowChars, false);
 
         writeSignatureToHeader(getterHeaderSignature);
 
         writeFunctionStartupBoilerPlate(getterSignature, true);
 
         generateGetterOrSetterBody(theField, CFieldName, isFieldStatic, false, aFieldTuple.mAnnotationInfo.narrowChars);
 
         // If field not final, also generate a setter function.
         if (!isFieldFinal) {
             String setterName = "set" + CFieldName;
 
             Class<?>[] setterArguments = new Class<?>[]{fieldType};
 
-            String setterSignature = Utils.getCImplementationMethodSignature(setterArguments, Void.class, setterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars);
-            String setterHeaderSignature = Utils.getCHeaderMethodSignature(setterArguments, SETTER_ARGUMENT_ANNOTATIONS, Void.class, setterName, mCClassName, isFieldStatic, aFieldTuple.mAnnotationInfo.narrowChars);
+            String setterSignature = Utils.getCImplementationMethodSignature(setterArguments, Void.class, setterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars, false);
+            String setterHeaderSignature = Utils.getCHeaderMethodSignature(setterArguments, SETTER_ARGUMENT_ANNOTATIONS, Void.class, setterName, mCClassName, isFieldStatic, aFieldTuple.mAnnotationInfo.narrowChars, false);
 
             writeSignatureToHeader(setterHeaderSignature);
 
             writeFunctionStartupBoilerPlate(setterSignature, true);
 
             generateGetterOrSetterBody(theField, CFieldName, isFieldStatic, true, aFieldTuple.mAnnotationInfo.narrowChars);
         }
     }
 
     public void generateConstructor(AnnotatableEntity aCtorTuple) {
         // Unpack the tuple and extract some useful fields from the Method..
         Constructor<?> theCtor = aCtorTuple.getConstructor();
         String CMethodName = mCClassName;
 
         generateMemberCommon(theCtor, mCClassName, mClassToWrap);
 
-        String implementationSignature = Utils.getCImplementationMethodSignature(theCtor.getParameterTypes(), Void.class, CMethodName, mCClassName, aCtorTuple.mAnnotationInfo.narrowChars);
-        String headerSignature = Utils.getCHeaderMethodSignature(theCtor.getParameterTypes(), theCtor.getParameterAnnotations(), Void.class, CMethodName, mCClassName, false, aCtorTuple.mAnnotationInfo.narrowChars);
+        String implementationSignature = Utils.getCImplementationMethodSignature(theCtor.getParameterTypes(), Void.class, CMethodName,
+            mCClassName, aCtorTuple.mAnnotationInfo.narrowChars, aCtorTuple.mAnnotationInfo.catchException);
+        String headerSignature = Utils.getCHeaderMethodSignature(theCtor.getParameterTypes(), theCtor.getParameterAnnotations(), Void.class, CMethodName,
+            mCClassName, false, aCtorTuple.mAnnotationInfo.narrowChars, aCtorTuple.mAnnotationInfo.catchException);
 
         // Slice off the "void " from the start of the constructor declaration.
         headerSignature = headerSignature.substring(5);
         implementationSignature = implementationSignature.substring(5);
 
         // Add the header signatures to the header file.
         writeSignatureToHeader(headerSignature);
 
         // Use the implementation signature to generate the method body...
         writeCtorBody(implementationSignature, theCtor,
             aCtorTuple.mAnnotationInfo.isMultithreaded,
-            aCtorTuple.mAnnotationInfo.noThrow);
+            aCtorTuple.mAnnotationInfo.noThrow,
+            aCtorTuple.mAnnotationInfo.catchException);
 
         if (theCtor.getParameterTypes().length == 0) {
             mHasEncounteredDefaultConstructor = true;
         }
     }
 
     public void generateMembers(Member[] members) {
         for (Member m : members) {
             if (!Modifier.isPublic(m.getModifiers())) {
                 continue;
             }
 
             String name = m.getName();
             name = name.substring(0, 1).toUpperCase() + name.substring(1);
 
-            AnnotationInfo info = new AnnotationInfo(name, true, true, true);
+            AnnotationInfo info = new AnnotationInfo(name, true, true, true, true);
             AnnotatableEntity entity = new AnnotatableEntity(m, info);
             if (m instanceof Constructor) {
                 generateConstructor(entity);
             } else if (m instanceof Method) {
                 generateMethod(entity);
             } else if (m instanceof Field) {
                 generateField(entity);
             } else {
@@ -403,18 +409,30 @@ public class CodeGenerator {
             if (needsNewline) {
                 wrapperMethodBodies.append('\n');
             }
         }
 
         return argumentContent;
     }
 
+    private void writeCatchException() {
+        wrapperMethodBodies.append(
+            "    if (env->ExceptionCheck()) {\n" +
+            "        env->ExceptionClear();\n" +
+            "        if (aResult) {\n" +
+            "            *aResult = NS_ERROR_FAILURE;\n" +
+            "        }\n" +
+            "    } else if (aResult) {\n" +
+            "        *aResult = NS_OK;\n" +
+            "    }\n\n");
+    }
+
     private void writeCtorBody(String implementationSignature, Constructor<?> theCtor,
-            boolean aIsThreaded, boolean aNoThrow) {
+            boolean aIsThreaded, boolean aNoThrow, boolean aCatchException) {
         Class<?>[] argumentTypes = theCtor.getParameterTypes();
 
         writeFunctionStartupBoilerPlate(implementationSignature, aIsThreaded);
 
         writeFramePushBoilerplate(theCtor, false, aNoThrow);
 
         if (mLazyInit) {
             writeMemberInit(theCtor, wrapperMethodBodies);
@@ -438,32 +456,38 @@ public class CodeGenerator {
 
 
         // Call takes class id, method id of constructor method, then arguments.
         wrapperMethodBodies.append(Utils.getClassReferenceName(mClassToWrap)).append(", ");
 
         wrapperMethodBodies.append(mMembersToIds.get(theCtor))
         // Tack on the arguments, if any..
                            .append(argumentContent)
-                           .append("), env);\n" +
-                                   "    env->PopLocalFrame(nullptr);\n" +
-                                   "}\n");
+                           .append("), env);\n");
+
+        // Check for exception and set aResult
+        if (aCatchException) {
+            writeCatchException();
+        }
+
+        wrapperMethodBodies.append("    env->PopLocalFrame(nullptr);\n}\n");
     }
 
     /**
      * Generates the method body of the C++ wrapper function for the Java method indicated.
      *
      * @param methodSignature The previously-generated C++ method signature for the method to be
      *                        generated.
      * @param aMethod         The Java method to be wrapped by the C++ method being generated.
      * @param aClass          The Java class to which the method belongs.
      */
     private void writeMethodBody(String methodSignature, Method aMethod,
                                  Class<?> aClass, boolean aIsMultithreaded,
-                                 boolean aNoThrow, boolean aNarrowChars) {
+                                 boolean aNoThrow, boolean aNarrowChars,
+                                 boolean aCatchException) {
         Class<?>[] argumentTypes = aMethod.getParameterTypes();
         Class<?> returnType = aMethod.getReturnType();
 
         writeFunctionStartupBoilerPlate(methodSignature, aIsMultithreaded);
 
         boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.isObjectType(returnType);
 
         writeFramePushBoilerplate(aMethod, isObjectReturningMethod, aNoThrow);
@@ -521,16 +545,21 @@ public class CodeGenerator {
         wrapperMethodBodies.append(argumentContent)
                            .append(");\n");
 
         // Check for exception and crash if any...
         if (!aNoThrow) {
             wrapperMethodBodies.append("    AndroidBridge::HandleUncaughtException(env);\n");
         }
 
+        // Check for exception and set aResult
+        if (aCatchException) {
+            writeCatchException();
+        }
+
         // If we're returning an object, pop the callee's stack frame extracting our ref as the return
         // value.
         if (isObjectReturningMethod) {
             wrapperMethodBodies.append("    ")
                                .append(Utils.getCReturnType(returnType, aNarrowChars))
                                .append(" ret = static_cast<").append(Utils.getCReturnType(returnType, aNarrowChars)).append(">(env->PopLocalFrame(temp));\n" +
                                        "    return ret;\n");
         } else if (!returnType.getCanonicalName().equals("void")) {
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -71,16 +71,17 @@ public class GeneratableElementIterator 
                 // WrappedJNIMethod has parameters. Use Reflection to obtain them.
                 Class<? extends Annotation> annotationType = annotation.annotationType();
                 final String annotationTypeName = annotationType.getName();
                 if (annotationTypeName.equals("org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI")) {
                     String stubName = null;
                     boolean isMultithreadedStub = false;
                     boolean noThrow = false;
                     boolean narrowChars = false;
+                    boolean catchException = false;
                     try {
                         // Determine the explicitly-given name of the stub to generate, if any.
                         final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
                         stubNameMethod.setAccessible(true);
                         stubName = (String) stubNameMethod.invoke(annotation);
 
                         // Determine if the generated stub is to allow calls from multiple threads.
                         final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread");
@@ -92,16 +93,21 @@ public class GeneratableElementIterator 
                         noThrowMethod.setAccessible(true);
                         noThrow = (Boolean) noThrowMethod.invoke(annotation);
 
                         // Determine if strings should be wide or narrow
                         final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars");
                         narrowCharsMethod.setAccessible(true);
                         narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
 
+                        // Determine if we should catch exceptions
+                        final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException");
+                        catchExceptionMethod.setAccessible(true);
+                        catchException = (Boolean) catchExceptionMethod.invoke(annotation);
+
                     } catch (NoSuchMethodException e) {
                         System.err.println("Unable to find expected field on WrapElementForJNI annotation. Did the signature change?");
                         e.printStackTrace(System.err);
                         System.exit(3);
                     } catch (IllegalAccessException e) {
                         System.err.println("IllegalAccessException reading fields on WrapElementForJNI annotation. Seems the semantics of Reflection have changed...");
                         e.printStackTrace(System.err);
                         System.exit(4);
@@ -113,27 +119,27 @@ public class GeneratableElementIterator 
 
                     // If the method name was not explicitly given in the annotation generate one...
                     if (stubName.isEmpty()) {
                         String aMethodName = candidateElement.getName();
                         stubName = aMethodName.substring(0, 1).toUpperCase() + aMethodName.substring(1);
                     }
 
                     AnnotationInfo annotationInfo = new AnnotationInfo(
-                        stubName, isMultithreadedStub, noThrow, narrowChars);
+                        stubName, isMultithreadedStub, noThrow, narrowChars, catchException);
                     mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
                     return;
                 }
             }
 
             // If no annotation found, we might be expected to generate anyway using default arguments,
             // thanks to the "Generate everything" annotation.
             if (mIterateEveryEntry) {
                 AnnotationInfo annotationInfo = new AnnotationInfo(
-                    candidateElement.getName(), false, false, false);
+                    candidateElement.getName(), false, false, false, false);
                 mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
                 return;
             }
         }
         mNextReturnValue = null;
     }
 
     @Override
--- a/build/annotationProcessors/utils/Utils.java
+++ b/build/annotationProcessors/utils/Utils.java
@@ -383,17 +383,18 @@ public class Utils {
      * generating header files and method bodies.
      *
      * @param aArgumentTypes Argument types of the Java method being wrapped.
      * @param aReturnType Return type of the Java method being wrapped.
      * @param aCMethodName Name of the method to generate in the C++ class.
      * @param aCClassName Name of the C++ class into which the method is declared.
      * @return The C++ method implementation signature for the method described.
      */
-    public static String getCImplementationMethodSignature(Class<?>[] aArgumentTypes, Class<?> aReturnType, String aCMethodName, String aCClassName, boolean aNarrowChars) {
+    public static String getCImplementationMethodSignature(Class<?>[] aArgumentTypes, Class<?> aReturnType,
+        String aCMethodName, String aCClassName, boolean aNarrowChars, boolean aCatchException) {
         StringBuilder retBuffer = new StringBuilder();
 
         retBuffer.append(getCReturnType(aReturnType, aNarrowChars));
         retBuffer.append(' ');
         retBuffer.append(aCClassName);
         retBuffer.append("::");
         retBuffer.append(aCMethodName);
         retBuffer.append('(');
@@ -405,16 +406,24 @@ public class Utils {
             // We, imaginatively, call our arguments a1, a2, a3...
             // The only way to preserve the names from Java would be to parse the
             // Java source, which would be computationally hard.
             retBuffer.append(aT);
             if (aT != aArgumentTypes.length - 1) {
                 retBuffer.append(", ");
             }
         }
+
+        if (aCatchException) {
+            if (aArgumentTypes.length > 0) {
+                retBuffer.append(", ");
+            }
+            retBuffer.append("nsresult* aResult");
+        }
+
         retBuffer.append(')');
         return retBuffer.toString();
     }
 
     /**
      * Produces a C method signature, sans semicolon, for the given Java Method. Useful for both
      * generating header files and method bodies.
      *
@@ -422,17 +431,18 @@ public class Utils {
      * @param aArgumentAnnotations The annotations on the Java method arguments. Used to specify
      *                             default values etc.
      * @param aReturnType Return type of the Java method being wrapped.
      * @param aCMethodName Name of the method to generate in the C++ class.
      * @param aCClassName Name of the C++ class into which the method is declared.e
      * @param aIsStaticStub true if the generated C++ method should be static, false otherwise.
      * @return The generated C++ header method signature for the method described.
      */
-    public static String getCHeaderMethodSignature(Class<?>[] aArgumentTypes, Annotation[][] aArgumentAnnotations, Class<?> aReturnType, String aCMethodName, String aCClassName, boolean aIsStaticStub, boolean aNarrowChars) {
+    public static String getCHeaderMethodSignature(Class<?>[] aArgumentTypes, Annotation[][] aArgumentAnnotations, Class<?> aReturnType,
+        String aCMethodName, String aCClassName, boolean aIsStaticStub, boolean aNarrowChars, boolean aCatchException) {
         StringBuilder retBuffer = new StringBuilder();
 
         // Add the static keyword, if applicable.
         if (aIsStaticStub) {
             retBuffer.append("static ");
         }
 
         // Write return type..
@@ -452,16 +462,24 @@ public class Utils {
 
             // Append the default value, if there is one..
             retBuffer.append(getDefaultValueString(aArgumentTypes[aT], aArgumentAnnotations[aT]));
 
             if (aT != aArgumentTypes.length - 1) {
                 retBuffer.append(", ");
             }
         }
+
+        if (aCatchException) {
+            if (aArgumentTypes.length > 0) {
+                retBuffer.append(", ");
+            }
+            retBuffer.append("nsresult* aResult = nullptr");
+        }
+
         retBuffer.append(')');
         return retBuffer.toString();
     }
 
     /**
      * If the given Annotation[] contains an OptionalGeneratedParameter annotation then return a
      * string assigning an argument of type aArgumentType to the default value for that type.
      * Otherwise, return the empty string.
--- a/mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
+++ b/mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
@@ -39,10 +39,19 @@ public @interface WrapElementForJNI {
     boolean allowMultithread() default false;
 
     /**
      * If set, the generated stub will not handle uncaught exceptions.
      * Any exception must be handled or cleared by the code calling the stub.
      */
     boolean noThrow() default false;
 
+    /**
+     * If set, uses UTF-8 strings
+     */
     boolean narrowChars() default false;
+
+    /**
+     * If set, the generated stub will catch any exception thrown and
+     * set a passed-in nsresult to NS_ERROR_FAILURE
+     */
+    boolean catchException() default false;
 }