CodeGenerator.java 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. package org.mozilla.gecko.annotationProcessors;
  5. import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
  6. import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
  7. import org.mozilla.gecko.annotationProcessors.utils.Utils;
  8. import java.lang.annotation.Annotation;
  9. import java.lang.reflect.Constructor;
  10. import java.lang.reflect.Field;
  11. import java.lang.reflect.Member;
  12. import java.lang.reflect.Method;
  13. import java.lang.reflect.Modifier;
  14. import java.util.HashSet;
  15. public class CodeGenerator {
  16. private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
  17. // Buffers holding the strings to ultimately be written to the output files.
  18. private final StringBuilder cpp = new StringBuilder();
  19. private final StringBuilder header = new StringBuilder();
  20. private final StringBuilder natives = new StringBuilder();
  21. private final StringBuilder nativesInits = new StringBuilder();
  22. private final Class<?> cls;
  23. private final String clsName;
  24. private AnnotationInfo.CallingThread callingThread = null;
  25. private int numNativesInits;
  26. private final HashSet<String> takenMethodNames = new HashSet<String>();
  27. public CodeGenerator(ClassWithOptions annotatedClass) {
  28. this.cls = annotatedClass.wrappedClass;
  29. this.clsName = annotatedClass.generatedName;
  30. final String unqualifiedName = Utils.getUnqualifiedName(clsName);
  31. header.append(
  32. "class " + clsName + " : public mozilla::jni::ObjectBase<" +
  33. unqualifiedName + ">\n" +
  34. "{\n" +
  35. "public:\n" +
  36. " static const char name[];\n" +
  37. "\n" +
  38. " explicit " + unqualifiedName + "(const Context& ctx) : ObjectBase<" +
  39. unqualifiedName + ">(ctx) {}\n" +
  40. "\n");
  41. cpp.append(
  42. "const char " + clsName + "::name[] =\n" +
  43. " \"" + cls.getName().replace('.', '/') + "\";\n" +
  44. "\n");
  45. natives.append(
  46. "template<class Impl>\n" +
  47. "class " + clsName + "::Natives : " +
  48. "public mozilla::jni::NativeImpl<" + unqualifiedName + ", Impl>\n" +
  49. "{\n" +
  50. "public:\n");
  51. }
  52. private String getTraitsName(String uniqueName, boolean includeScope) {
  53. return (includeScope ? clsName + "::" : "") + uniqueName + "_t";
  54. }
  55. /**
  56. * Return the C++ type name for this class or any class within the chain
  57. * of declaring classes, if the target class matches the given type.
  58. *
  59. * Return null if the given type does not match any class searched.
  60. */
  61. private String getMatchingClassType(final Class<?> type) {
  62. Class<?> cls = this.cls;
  63. String clsName = this.clsName;
  64. while (cls != null) {
  65. if (type.equals(cls)) {
  66. return clsName;
  67. }
  68. cls = cls.getDeclaringClass();
  69. clsName = clsName.substring(0, Math.max(0, clsName.lastIndexOf("::")));
  70. }
  71. return null;
  72. }
  73. private String getNativeParameterType(Class<?> type, AnnotationInfo info) {
  74. final String clsName = getMatchingClassType(type);
  75. if (clsName != null) {
  76. return Utils.getUnqualifiedName(clsName) + "::Param";
  77. }
  78. return Utils.getNativeParameterType(type, info);
  79. }
  80. private String getNativeReturnType(Class<?> type, AnnotationInfo info) {
  81. final String clsName = getMatchingClassType(type);
  82. if (clsName != null) {
  83. return Utils.getUnqualifiedName(clsName) + "::LocalRef";
  84. }
  85. return Utils.getNativeReturnType(type, info);
  86. }
  87. private void generateMember(AnnotationInfo info, Member member,
  88. String uniqueName, Class<?> type, Class<?>[] argTypes) {
  89. final StringBuilder args = new StringBuilder();
  90. for (Class<?> argType : argTypes) {
  91. args.append("\n " + getNativeParameterType(argType, info) + ",");
  92. }
  93. if (args.length() > 0) {
  94. args.setLength(args.length() - 1);
  95. }
  96. header.append(
  97. " struct " + getTraitsName(uniqueName, /* includeScope */ false) + " {\n" +
  98. " typedef " + Utils.getUnqualifiedName(clsName) + " Owner;\n" +
  99. " typedef " + getNativeReturnType(type, info) + " ReturnType;\n" +
  100. " typedef " + getNativeParameterType(type, info) + " SetterType;\n" +
  101. " typedef mozilla::jni::Args<" + args + "> Args;\n" +
  102. " static constexpr char name[] = \"" +
  103. Utils.getMemberName(member) + "\";\n" +
  104. " static constexpr char signature[] =\n" +
  105. " \"" + Utils.getSignature(member) + "\";\n" +
  106. " static const bool isStatic = " + Utils.isStatic(member) + ";\n" +
  107. " static const mozilla::jni::ExceptionMode exceptionMode =\n" +
  108. " " + info.exceptionMode.nativeValue() + ";\n" +
  109. " static const mozilla::jni::CallingThread callingThread =\n" +
  110. " " + info.callingThread.nativeValue() + ";\n" +
  111. " static const mozilla::jni::DispatchTarget dispatchTarget =\n" +
  112. " " + info.dispatchTarget.nativeValue() + ";\n" +
  113. " };\n" +
  114. "\n");
  115. cpp.append(
  116. "constexpr char " + getTraitsName(uniqueName, /* includeScope */ true) +
  117. "::name[];\n" +
  118. "constexpr char " + getTraitsName(uniqueName, /* includeScope */ true) +
  119. "::signature[];\n" +
  120. "\n");
  121. if (this.callingThread == null) {
  122. this.callingThread = info.callingThread;
  123. } else if (this.callingThread != info.callingThread) {
  124. // We have a mix of calling threads, so specify "any" for the whole class.
  125. this.callingThread = AnnotationInfo.CallingThread.ANY;
  126. }
  127. }
  128. private String getUniqueMethodName(String basename) {
  129. String newName = basename;
  130. int index = 1;
  131. while (takenMethodNames.contains(newName)) {
  132. newName = basename + (++index);
  133. }
  134. takenMethodNames.add(newName);
  135. return newName;
  136. }
  137. /**
  138. * Generate a method prototype that includes return and argument types,
  139. * without specifiers (static, const, etc.).
  140. */
  141. private String generatePrototype(String name, Class<?>[] argTypes,
  142. Class<?> returnType, AnnotationInfo info,
  143. boolean includeScope, boolean includeArgName,
  144. boolean isConst) {
  145. final StringBuilder proto = new StringBuilder();
  146. int argIndex = 0;
  147. proto.append("auto ");
  148. if (includeScope) {
  149. proto.append(clsName).append("::");
  150. }
  151. proto.append(name).append('(');
  152. for (Class<?> argType : argTypes) {
  153. proto.append(getNativeParameterType(argType, info));
  154. if (includeArgName) {
  155. proto.append(" a").append(argIndex++);
  156. }
  157. proto.append(", ");
  158. }
  159. if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT &&
  160. !returnType.equals(void.class)) {
  161. proto.append(getNativeReturnType(returnType, info)).append('*');
  162. if (includeArgName) {
  163. proto.append(" a").append(argIndex++);
  164. }
  165. proto.append(", ");
  166. }
  167. if (proto.substring(proto.length() - 2).equals(", ")) {
  168. proto.setLength(proto.length() - 2);
  169. }
  170. proto.append(')');
  171. if (isConst) {
  172. proto.append(" const");
  173. }
  174. if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
  175. proto.append(" -> nsresult");
  176. } else {
  177. proto.append(" -> ").append(getNativeReturnType(returnType, info));
  178. }
  179. return proto.toString();
  180. }
  181. /**
  182. * Generate a method declaration that includes the prototype with specifiers,
  183. * but without the method body.
  184. */
  185. private String generateDeclaration(String name, Class<?>[] argTypes,
  186. Class<?> returnType, AnnotationInfo info,
  187. boolean isStatic) {
  188. return (isStatic ? "static " : "") +
  189. generatePrototype(name, argTypes, returnType, info,
  190. /* includeScope */ false, /* includeArgName */ false,
  191. /* isConst */ !isStatic) + ';';
  192. }
  193. /**
  194. * Generate a method definition that includes the prototype with specifiers,
  195. * and with the method body.
  196. */
  197. private String generateDefinition(String accessorName, String name, Class<?>[] argTypes,
  198. Class<?> returnType, AnnotationInfo info, boolean isStatic) {
  199. final StringBuilder def = new StringBuilder(
  200. generatePrototype(name, argTypes, returnType, info,
  201. /* includeScope */ true, /* includeArgName */ true,
  202. /* isConst */ !isStatic));
  203. def.append("\n{\n");
  204. // Generate code to handle the return value, if needed.
  205. // We initialize rv to NS_OK instead of NS_ERROR_* because loading NS_OK (0) uses
  206. // fewer instructions. We are guaranteed to set rv to the correct value later.
  207. if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT &&
  208. returnType.equals(void.class)) {
  209. def.append(
  210. " nsresult rv = NS_OK;\n" +
  211. " ");
  212. } else if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
  213. // Non-void return type
  214. final String resultArg = "a" + argTypes.length;
  215. def.append(
  216. " MOZ_ASSERT(" + resultArg + ");\n" +
  217. " nsresult rv = NS_OK;\n" +
  218. " *" + resultArg + " = ");
  219. } else {
  220. def.append(
  221. " return ");
  222. }
  223. // Generate a call, e.g., Method<Traits>::Call(a0, a1, a2);
  224. def.append(accessorName).append("(")
  225. .append(Utils.getUnqualifiedName(clsName) +
  226. (isStatic ? "::Context()" : "::mCtx"));
  227. if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
  228. def.append(", &rv");
  229. } else {
  230. def.append(", nullptr");
  231. }
  232. // Generate the call argument list.
  233. for (int argIndex = 0; argIndex < argTypes.length; argIndex++) {
  234. def.append(", a").append(argIndex);
  235. }
  236. def.append(");\n");
  237. if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
  238. def.append(" return rv;\n");
  239. }
  240. return def.append("}").toString();
  241. }
  242. /**
  243. * Append the appropriate generated code to the buffers for the method provided.
  244. *
  245. * @param annotatedMethod The Java method, plus annotation data.
  246. */
  247. public void generateMethod(AnnotatableEntity annotatedMethod) {
  248. // Unpack the tuple and extract some useful fields from the Method..
  249. final Method method = annotatedMethod.getMethod();
  250. final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
  251. final String uniqueName = getUniqueMethodName(info.wrapperName);
  252. final Class<?>[] argTypes = method.getParameterTypes();
  253. final Class<?> returnType = method.getReturnType();
  254. if (method.isSynthetic()) {
  255. return;
  256. }
  257. // Sanity check
  258. if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
  259. throw new IllegalStateException("Invalid dispatch target \"" +
  260. info.dispatchTarget.name().toLowerCase() +
  261. "\" for non-native method " + clsName + "::" + uniqueName);
  262. }
  263. generateMember(info, method, uniqueName, returnType, argTypes);
  264. final boolean isStatic = Utils.isStatic(method);
  265. header.append(
  266. " " + generateDeclaration(info.wrapperName, argTypes,
  267. returnType, info, isStatic) + "\n" +
  268. "\n");
  269. cpp.append(
  270. generateDefinition(
  271. "mozilla::jni::Method<" +
  272. getTraitsName(uniqueName, /* includeScope */ false) + ">::Call",
  273. info.wrapperName, argTypes, returnType, info, isStatic) + "\n" +
  274. "\n");
  275. }
  276. /**
  277. * Append the appropriate generated code to the buffers for the native method provided.
  278. *
  279. * @param annotatedMethod The Java native method, plus annotation data.
  280. */
  281. public void generateNative(AnnotatableEntity annotatedMethod) {
  282. // Unpack the tuple and extract some useful fields from the Method..
  283. final Method method = annotatedMethod.getMethod();
  284. final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
  285. final String uniqueName = getUniqueMethodName(info.wrapperName);
  286. final Class<?>[] argTypes = method.getParameterTypes();
  287. final Class<?> returnType = method.getReturnType();
  288. // Sanity check
  289. if (info.exceptionMode != AnnotationInfo.ExceptionMode.ABORT &&
  290. info.exceptionMode != AnnotationInfo.ExceptionMode.IGNORE) {
  291. throw new IllegalStateException("Invalid exception mode \"" +
  292. info.exceptionMode.name().toLowerCase() +
  293. "\" for native method " + clsName + "::" + uniqueName);
  294. }
  295. if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT &&
  296. returnType != void.class) {
  297. throw new IllegalStateException(
  298. "Must return void when not dispatching to current thread for native method " +
  299. clsName + "::" + uniqueName);
  300. }
  301. generateMember(info, method, uniqueName, returnType, argTypes);
  302. final String traits = getTraitsName(uniqueName, /* includeScope */ true);
  303. if (nativesInits.length() > 0) {
  304. nativesInits.append(',');
  305. }
  306. nativesInits.append(
  307. "\n" +
  308. "\n" +
  309. " mozilla::jni::MakeNativeMethod<" + traits + ">(\n" +
  310. " mozilla::jni::NativeStub<" + traits + ", Impl>\n" +
  311. " ::template Wrap<&Impl::" + info.wrapperName + ">)");
  312. numNativesInits++;
  313. }
  314. private String getLiteral(Object val, AnnotationInfo info) {
  315. final Class<?> type = val.getClass();
  316. if (type.equals(char.class) || type.equals(Character.class)) {
  317. final char c = (char) val;
  318. if (c >= 0x20 && c < 0x7F) {
  319. return "'" + c + '\'';
  320. }
  321. return "u'\\u" + Integer.toHexString(0x10000 | (int) c).substring(1) + '\'';
  322. } else if (type.equals(CharSequence.class) || type.equals(String.class)) {
  323. final CharSequence str = (CharSequence) val;
  324. final StringBuilder out = new StringBuilder("u\"");
  325. for (int i = 0; i < str.length(); i++) {
  326. final char c = str.charAt(i);
  327. if (c >= 0x20 && c < 0x7F) {
  328. out.append(c);
  329. } else {
  330. out.append("\\u").append(Integer.toHexString(0x10000 | (int) c).substring(1));
  331. }
  332. }
  333. return out.append('"').toString();
  334. }
  335. return String.valueOf(val);
  336. }
  337. public void generateField(AnnotatableEntity annotatedField) {
  338. final Field field = annotatedField.getField();
  339. final AnnotationInfo info = annotatedField.mAnnotationInfo;
  340. final String uniqueName = info.wrapperName;
  341. final Class<?> type = field.getType();
  342. // Handles a peculiar case when dealing with enum types. We don't care about this field.
  343. // It just gets in the way and stops our code from compiling.
  344. if (field.isSynthetic() || field.getName().equals("$VALUES")) {
  345. return;
  346. }
  347. // Sanity check
  348. if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
  349. throw new IllegalStateException("Invalid dispatch target \"" +
  350. info.dispatchTarget.name().toLowerCase() +
  351. "\" for field " + clsName + "::" + uniqueName);
  352. }
  353. final boolean isStatic = Utils.isStatic(field);
  354. final boolean isFinal = Utils.isFinal(field);
  355. if (isStatic && isFinal && (type.isPrimitive() || type.equals(String.class))) {
  356. Object val = null;
  357. try {
  358. field.setAccessible(true);
  359. val = field.get(null);
  360. } catch (final IllegalAccessException e) {
  361. }
  362. if (val != null && type.isPrimitive()) {
  363. // For static final primitive fields, we can use a "static const" declaration.
  364. header.append(
  365. " static const " + Utils.getNativeReturnType(type, info) +
  366. ' ' + info.wrapperName + " = " + getLiteral(val, info) + ";\n" +
  367. "\n");
  368. return;
  369. } else if (val != null && type.equals(String.class)) {
  370. final String nativeType = "char16_t";
  371. header.append(
  372. " static const " + nativeType + ' ' + info.wrapperName + "[];\n" +
  373. "\n");
  374. cpp.append(
  375. "const " + nativeType + ' ' + clsName + "::" + info.wrapperName +
  376. "[] = " + getLiteral(val, info) + ";\n" +
  377. "\n");
  378. return;
  379. }
  380. // Fall back to using accessors if we encounter an exception.
  381. }
  382. generateMember(info, field, uniqueName, type, EMPTY_CLASS_ARRAY);
  383. final Class<?>[] getterArgs = EMPTY_CLASS_ARRAY;
  384. header.append(
  385. " " + generateDeclaration(info.wrapperName, getterArgs,
  386. type, info, isStatic) + "\n" +
  387. "\n");
  388. cpp.append(
  389. generateDefinition(
  390. "mozilla::jni::Field<" +
  391. getTraitsName(uniqueName, /* includeScope */ false) + ">::Get",
  392. info.wrapperName, getterArgs, type, info, isStatic) + "\n" +
  393. "\n");
  394. if (isFinal) {
  395. return;
  396. }
  397. final Class<?>[] setterArgs = new Class<?>[] { type };
  398. header.append(
  399. " " + generateDeclaration(info.wrapperName, setterArgs,
  400. void.class, info, isStatic) + "\n" +
  401. "\n");
  402. cpp.append(
  403. generateDefinition(
  404. "mozilla::jni::Field<" +
  405. getTraitsName(uniqueName, /* includeScope */ false) + ">::Set",
  406. info.wrapperName, setterArgs, void.class, info, isStatic) + "\n" +
  407. "\n");
  408. }
  409. public void generateConstructor(AnnotatableEntity annotatedConstructor) {
  410. // Unpack the tuple and extract some useful fields from the Method..
  411. final Constructor<?> method = annotatedConstructor.getConstructor();
  412. final AnnotationInfo info = annotatedConstructor.mAnnotationInfo;
  413. final String wrapperName = "New";
  414. final String uniqueName = getUniqueMethodName(wrapperName);
  415. final Class<?>[] argTypes = method.getParameterTypes();
  416. final Class<?> returnType = cls;
  417. if (method.isSynthetic()) {
  418. return;
  419. }
  420. // Sanity check
  421. if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
  422. throw new IllegalStateException("Invalid dispatch target \"" +
  423. info.dispatchTarget.name().toLowerCase() +
  424. "\" for constructor " + clsName + "::" + uniqueName);
  425. }
  426. generateMember(info, method, uniqueName, returnType, argTypes);
  427. header.append(
  428. " " + generateDeclaration(wrapperName, argTypes,
  429. returnType, info, /* isStatic */ true) + "\n" +
  430. "\n");
  431. cpp.append(
  432. generateDefinition(
  433. "mozilla::jni::Constructor<" +
  434. getTraitsName(uniqueName, /* includeScope */ false) + ">::Call",
  435. wrapperName, argTypes, returnType, info, /* isStatic */ true) + "\n" +
  436. "\n");
  437. }
  438. public void generateMembers(Member[] members) {
  439. for (Member m : members) {
  440. if (!Modifier.isPublic(m.getModifiers())) {
  441. continue;
  442. }
  443. String name = Utils.getMemberName(m);
  444. name = name.substring(0, 1).toUpperCase() + name.substring(1);
  445. // Default for SDK bindings.
  446. final AnnotationInfo info = new AnnotationInfo(name,
  447. AnnotationInfo.ExceptionMode.NSRESULT,
  448. AnnotationInfo.CallingThread.ANY,
  449. AnnotationInfo.DispatchTarget.CURRENT);
  450. final AnnotatableEntity entity = new AnnotatableEntity(m, info);
  451. if (m instanceof Constructor) {
  452. generateConstructor(entity);
  453. } else if (m instanceof Method) {
  454. generateMethod(entity);
  455. } else if (m instanceof Field) {
  456. generateField(entity);
  457. } else {
  458. throw new IllegalArgumentException(
  459. "expected member to be Constructor, Method, or Field");
  460. }
  461. }
  462. }
  463. public void generateClasses(final ClassWithOptions[] classes) {
  464. if (classes.length == 0) {
  465. return;
  466. }
  467. for (final ClassWithOptions cls : classes) {
  468. // Extract "Inner" from "Outer::Inner".
  469. header.append(
  470. " class " + Utils.getUnqualifiedName(cls.generatedName) + ";\n");
  471. }
  472. header.append('\n');
  473. }
  474. /**
  475. * Get the finalised bytes to go into the generated wrappers file.
  476. *
  477. * @return The bytes to be written to the wrappers file.
  478. */
  479. public String getWrapperFileContents() {
  480. return cpp.toString();
  481. }
  482. /**
  483. * Get the finalised bytes to go into the generated header file.
  484. *
  485. * @return The bytes to be written to the header file.
  486. */
  487. public String getHeaderFileContents() {
  488. if (this.callingThread == null) {
  489. this.callingThread = AnnotationInfo.CallingThread.ANY;
  490. }
  491. header.append(
  492. " static const mozilla::jni::CallingThread callingThread =\n" +
  493. " " + this.callingThread.nativeValue() + ";\n" +
  494. "\n");
  495. if (nativesInits.length() > 0) {
  496. header.append(
  497. " template<class Impl> class Natives;\n");
  498. }
  499. header.append(
  500. "};\n" +
  501. "\n");
  502. return header.toString();
  503. }
  504. /**
  505. * Get the finalised bytes to go into the generated natives header file.
  506. *
  507. * @return The bytes to be written to the header file.
  508. */
  509. public String getNativesFileContents() {
  510. if (nativesInits.length() == 0) {
  511. return "";
  512. }
  513. natives.append(
  514. " static const JNINativeMethod methods[" + numNativesInits + "];\n" +
  515. "};\n" +
  516. "\n" +
  517. "template<class Impl>\n" +
  518. "const JNINativeMethod " + clsName + "::Natives<Impl>::methods[] = {" + nativesInits + '\n' +
  519. "};\n" +
  520. "\n");
  521. return natives.toString();
  522. }
  523. }