@futpib/parser 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/.github/copilot-instructions.md +149 -0
  2. package/.github/workflows/copilot-setup-steps.yml +18 -0
  3. package/.github/workflows/main.yml +29 -8
  4. package/.yarn/releases/yarn-4.9.4.cjs +942 -0
  5. package/.yarnrc.yml +1 -1
  6. package/build/allSettledStream.js +1 -1
  7. package/build/allSettledStream.test.js +2 -2
  8. package/build/androidPackageParser.d.ts +1 -1
  9. package/build/androidPackageParser.js +5 -3
  10. package/build/androidPackageParser.test.js +7 -7
  11. package/build/androidPackageUnparser.d.ts +2 -2
  12. package/build/androidPackageUnparser.js +18 -14
  13. package/build/androidPackageUnparser.test.js +7 -7
  14. package/build/arbitrarilySlicedAsyncInterator.js +2 -1
  15. package/build/arbitraryDalvikBytecode.d.ts +4 -0
  16. package/build/arbitraryDalvikBytecode.js +640 -0
  17. package/build/arbitraryDalvikExecutable.d.ts +3 -0
  18. package/build/arbitraryDalvikExecutable.js +282 -0
  19. package/build/arbitraryDosDateTime.js +1 -0
  20. package/build/arbitraryZipStream.js +1 -1
  21. package/build/arrayParser.js +2 -2
  22. package/build/arrayUnparser.d.ts +1 -1
  23. package/build/backsmali.d.ts +3 -1
  24. package/build/backsmali.js +31 -3
  25. package/build/customInvariant.d.ts +2 -1
  26. package/build/customInvariant.js +4 -6
  27. package/build/dalvikBytecodeParser/formatParsers.d.ts +76 -2
  28. package/build/dalvikBytecodeParser/formatParsers.js +146 -11
  29. package/build/dalvikBytecodeParser/formatSizes.d.ts +34 -0
  30. package/build/dalvikBytecodeParser/formatSizes.js +34 -0
  31. package/build/dalvikBytecodeParser/operationFormats.d.ts +225 -0
  32. package/build/dalvikBytecodeParser/operationFormats.js +225 -0
  33. package/build/dalvikBytecodeParser.d.ts +1105 -5
  34. package/build/dalvikBytecodeParser.js +658 -205
  35. package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +152 -0
  36. package/build/dalvikBytecodeUnparser/formatUnparsers.js +225 -0
  37. package/build/dalvikBytecodeUnparser.d.ts +3 -0
  38. package/build/dalvikBytecodeUnparser.js +642 -0
  39. package/build/dalvikBytecodeUnparser.test.d.ts +1 -0
  40. package/build/dalvikBytecodeUnparser.test.js +25 -0
  41. package/build/dalvikExecutable.d.ts +65 -8
  42. package/build/dalvikExecutable.js +36 -0
  43. package/build/dalvikExecutableParser/stringSyntaxParser.d.ts +1 -1
  44. package/build/dalvikExecutableParser/stringSyntaxParser.js +17 -17
  45. package/build/dalvikExecutableParser/typeParsers.d.ts +2 -1
  46. package/build/dalvikExecutableParser/typeParsers.js +16 -11
  47. package/build/dalvikExecutableParser/typedNumbers.d.ts +85 -69
  48. package/build/dalvikExecutableParser/typedNumbers.js +0 -1
  49. package/build/dalvikExecutableParser.d.ts +2 -2
  50. package/build/dalvikExecutableParser.js +655 -337
  51. package/build/dalvikExecutableParser.test.js +24 -22
  52. package/build/dalvikExecutableParserAgainstSmaliParser.test.js +223 -246
  53. package/build/dalvikExecutableUnparser/annotationUnparsers.d.ts +14 -0
  54. package/build/dalvikExecutableUnparser/annotationUnparsers.js +97 -0
  55. package/build/dalvikExecutableUnparser/poolBuilders.d.ts +49 -0
  56. package/build/dalvikExecutableUnparser/poolBuilders.js +140 -0
  57. package/build/dalvikExecutableUnparser/poolScanners.d.ts +4 -0
  58. package/build/dalvikExecutableUnparser/poolScanners.js +220 -0
  59. package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +25 -0
  60. package/build/dalvikExecutableUnparser/sectionUnparsers.js +581 -0
  61. package/build/dalvikExecutableUnparser/utils.d.ts +10 -0
  62. package/build/dalvikExecutableUnparser/utils.js +108 -0
  63. package/build/dalvikExecutableUnparser.d.ts +4 -0
  64. package/build/dalvikExecutableUnparser.js +406 -0
  65. package/build/dalvikExecutableUnparser.test.d.ts +1 -0
  66. package/build/dalvikExecutableUnparser.test.js +31 -0
  67. package/build/debugLogInputParser.js +1 -1
  68. package/build/disjunctionParser.d.ts +2 -2
  69. package/build/disjunctionParser.js +2 -2
  70. package/build/elementTerminatedArrayParser.d.ts +2 -2
  71. package/build/elementTerminatedArrayParser.js +1 -1
  72. package/build/elementTerminatedArrayParser.test.js +5 -5
  73. package/build/elementTerminatedSequenceArrayParser.d.ts +2 -2
  74. package/build/elementTerminatedSequenceArrayParser.js +1 -1
  75. package/build/elementTerminatedSequenceArrayParser.test.js +2 -2
  76. package/build/elementTerminatedSequenceParser.d.ts +2 -2
  77. package/build/elementTerminatedSequenceParser.js +1 -1
  78. package/build/elementTerminatedSequenceParser.test.js +2 -2
  79. package/build/endOfInputParser.d.ts +1 -1
  80. package/build/exactElementSwitchParser.d.ts +3 -0
  81. package/build/exactElementSwitchParser.js +22 -0
  82. package/build/fetchCid.js +2 -6
  83. package/build/fetchCid.test.d.ts +1 -0
  84. package/build/fetchCid.test.js +16 -0
  85. package/build/fixedLengthSequenceParser.test.js +2 -2
  86. package/build/hasExecutable.js +2 -2
  87. package/build/highResolutionTimer.js +1 -1
  88. package/build/inputReader.d.ts +1 -1
  89. package/build/inputReader.test.js +33 -45
  90. package/build/javaKeyStoreParser.test.js +6 -6
  91. package/build/jsonParser.js +8 -8
  92. package/build/lazyMessageError.d.ts +48 -0
  93. package/build/lazyMessageError.js +53 -0
  94. package/build/lazyMessageError.test.d.ts +1 -0
  95. package/build/lazyMessageError.test.js +15 -0
  96. package/build/leb128Parser.d.ts +1 -1
  97. package/build/leb128Parser.js +10 -10
  98. package/build/leb128Parser.test.js +7 -7
  99. package/build/negativeLookaheadParser.js +2 -2
  100. package/build/negativeLookaheadParser.test.js +4 -4
  101. package/build/noStackCaptureOverheadError.d.ts +4 -0
  102. package/build/noStackCaptureOverheadError.js +9 -0
  103. package/build/noStackCaptureOverheadError.test.d.ts +1 -0
  104. package/build/noStackCaptureOverheadError.test.js +15 -0
  105. package/build/nonEmptyArrayParser.js +2 -2
  106. package/build/nonEmptyArrayParser.test.js +2 -1
  107. package/build/optionalParser.js +2 -2
  108. package/build/parser.d.ts +2 -1
  109. package/build/parser.js +23 -8
  110. package/build/parser.test.js +78 -29
  111. package/build/parserConsumedSequenceParser.d.ts +1 -1
  112. package/build/parserConsumedSequenceParser.js +2 -2
  113. package/build/parserContext.d.ts +8 -6
  114. package/build/parserContext.js +60 -33
  115. package/build/parserContext.test.js +7 -3
  116. package/build/parserError.d.ts +603 -44
  117. package/build/parserError.js +98 -53
  118. package/build/parserImplementationInvariant.d.ts +1 -1
  119. package/build/parserImplementationInvariant.js +2 -2
  120. package/build/parserInputCompanion.js +2 -2
  121. package/build/promiseCompose.js +1 -2
  122. package/build/separatedArrayParser.js +2 -2
  123. package/build/separatedNonEmptyArrayParser.d.ts +2 -0
  124. package/build/separatedNonEmptyArrayParser.js +40 -0
  125. package/build/separatedNonEmptyArrayParser.test.d.ts +1 -0
  126. package/build/separatedNonEmptyArrayParser.test.js +66 -0
  127. package/build/sequenceBuffer.js +1 -1
  128. package/build/sequenceTerminatedSequenceParser.d.ts +2 -2
  129. package/build/sequenceTerminatedSequenceParser.js +3 -3
  130. package/build/sequenceTerminatedSequenceParser.test.js +1 -1
  131. package/build/sequenceUnparser.d.ts +1 -1
  132. package/build/skipToParser.d.ts +1 -1
  133. package/build/skipToParser.js +2 -2
  134. package/build/sliceBoundedParser.test.js +4 -9
  135. package/build/smali.d.ts +1 -1
  136. package/build/smali.js +6 -2
  137. package/build/smaliParser.d.ts +62 -6
  138. package/build/smaliParser.js +1721 -296
  139. package/build/smaliParser.test.js +338 -43
  140. package/build/stringFromAsyncIterable.d.ts +1 -0
  141. package/build/stringFromAsyncIterable.js +7 -0
  142. package/build/terminatedArrayParser.js +4 -4
  143. package/build/terminatedArrayParser.test.js +7 -7
  144. package/build/toAsyncIterator.js +4 -4
  145. package/build/unionParser.d.ts +1 -1
  146. package/build/unionParser.js +2 -2
  147. package/build/unionParser.test.js +3 -3
  148. package/build/unparser.d.ts +3 -3
  149. package/build/unparser.js +6 -4
  150. package/build/unparser.test.js +7 -19
  151. package/build/unparserContext.d.ts +2 -2
  152. package/build/unparserContext.js +2 -3
  153. package/build/unparserError.d.ts +2 -1
  154. package/build/unparserError.js +2 -1
  155. package/build/unparserImplementationInvariant.d.ts +1 -1
  156. package/build/unparserOutputCompanion.d.ts +1 -1
  157. package/build/unparserOutputCompanion.js +1 -1
  158. package/build/zipParser.js +1 -1
  159. package/build/zipUnparser.d.ts +3 -3
  160. package/build/zipUnparser.js +9 -19
  161. package/build/zipUnparser.test.js +1 -1
  162. package/package.json +19 -26
  163. package/src/allSettledStream.test.ts +2 -2
  164. package/src/allSettledStream.ts +3 -3
  165. package/src/androidPackageParser.test.ts +17 -19
  166. package/src/androidPackageParser.ts +129 -171
  167. package/src/androidPackageUnparser.test.ts +19 -21
  168. package/src/androidPackageUnparser.ts +23 -17
  169. package/src/arbitrarilySlicedAsyncInterable.ts +1 -1
  170. package/src/arbitrarilySlicedAsyncInterator.ts +4 -4
  171. package/src/arbitraryDalvikBytecode.ts +992 -0
  172. package/src/arbitraryDalvikExecutable.ts +434 -0
  173. package/src/arbitraryDosDateTime.ts +1 -0
  174. package/src/arbitraryZipStream.ts +1 -1
  175. package/src/arrayParser.ts +2 -2
  176. package/src/arrayUnparser.ts +2 -2
  177. package/src/backsmali.ts +48 -4
  178. package/src/bsonParser.test.ts +12 -14
  179. package/src/customInvariant.ts +8 -12
  180. package/src/dalvikBytecodeParser/formatParsers.ts +376 -17
  181. package/src/dalvikBytecodeParser/formatSizes.ts +35 -0
  182. package/src/dalvikBytecodeParser/operationFormats.ts +226 -0
  183. package/src/dalvikBytecodeParser.ts +1042 -243
  184. package/src/dalvikBytecodeUnparser/formatUnparsers.ts +442 -0
  185. package/src/dalvikBytecodeUnparser.test.ts +44 -0
  186. package/src/dalvikBytecodeUnparser.ts +758 -0
  187. package/src/dalvikExecutable.ts +110 -48
  188. package/src/dalvikExecutableParser/stringSyntaxParser.ts +33 -33
  189. package/src/dalvikExecutableParser/typeParsers.ts +23 -14
  190. package/src/dalvikExecutableParser/typedNumbers.ts +19 -19
  191. package/src/dalvikExecutableParser.test.ts +60 -60
  192. package/src/dalvikExecutableParser.test.ts.md +6 -6
  193. package/src/dalvikExecutableParser.test.ts.snap +0 -0
  194. package/src/dalvikExecutableParser.ts +911 -434
  195. package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +256 -239
  196. package/src/dalvikExecutableUnparser/annotationUnparsers.ts +135 -0
  197. package/src/dalvikExecutableUnparser/poolBuilders.ts +189 -0
  198. package/src/dalvikExecutableUnparser/poolScanners.ts +297 -0
  199. package/src/dalvikExecutableUnparser/sectionUnparsers.ts +683 -0
  200. package/src/dalvikExecutableUnparser/utils.ts +149 -0
  201. package/src/dalvikExecutableUnparser.test.ts +57 -0
  202. package/src/dalvikExecutableUnparser.ts +581 -0
  203. package/src/debugLogInputParser.ts +1 -1
  204. package/src/disjunctionParser.ts +5 -5
  205. package/src/elementTerminatedArrayParser.test.ts +8 -8
  206. package/src/elementTerminatedArrayParser.ts +2 -2
  207. package/src/elementTerminatedSequenceArrayParser.test.ts +4 -6
  208. package/src/elementTerminatedSequenceArrayParser.ts +2 -2
  209. package/src/elementTerminatedSequenceParser.test.ts +4 -6
  210. package/src/elementTerminatedSequenceParser.ts +2 -2
  211. package/src/endOfInputParser.ts +1 -1
  212. package/src/exactElementSwitchParser.ts +41 -0
  213. package/src/fetchCid.test.ts +20 -0
  214. package/src/fetchCid.ts +3 -7
  215. package/src/fixedLengthSequenceParser.test.ts +10 -12
  216. package/src/hasExecutable.ts +2 -2
  217. package/src/highResolutionTimer.ts +1 -1
  218. package/src/inputReader.test.ts +39 -52
  219. package/src/inputReader.ts +2 -4
  220. package/src/inputReaderState.ts +1 -1
  221. package/src/inspect.ts +1 -1
  222. package/src/javaKeyStoreParser.test.ts +12 -14
  223. package/src/javaKeyStoreParser.ts +2 -6
  224. package/src/jsonParser.test.ts +2 -4
  225. package/src/jsonParser.ts +34 -38
  226. package/src/lazyMessageError.test.ts +21 -0
  227. package/src/lazyMessageError.ts +88 -0
  228. package/src/leb128Parser.test.ts +25 -23
  229. package/src/leb128Parser.ts +19 -19
  230. package/src/negativeLookaheadParser.test.ts +7 -11
  231. package/src/negativeLookaheadParser.ts +2 -2
  232. package/src/noStackCaptureOverheadError.test.ts +17 -0
  233. package/src/noStackCaptureOverheadError.ts +12 -0
  234. package/src/nonEmptyArrayParser.test.ts +3 -2
  235. package/src/nonEmptyArrayParser.ts +2 -2
  236. package/src/optionalParser.ts +2 -2
  237. package/src/parser.test.ts +96 -43
  238. package/src/parser.test.ts.md +13 -6
  239. package/src/parser.test.ts.snap +0 -0
  240. package/src/parser.ts +35 -12
  241. package/src/parserAccessorParser.ts +1 -1
  242. package/src/parserConsumedSequenceParser.ts +3 -3
  243. package/src/parserContext.test.ts +7 -3
  244. package/src/parserContext.ts +82 -48
  245. package/src/parserError.ts +143 -63
  246. package/src/parserImplementationInvariant.ts +3 -3
  247. package/src/parserInputCompanion.ts +2 -2
  248. package/src/promiseCompose.ts +2 -2
  249. package/src/separatedArrayParser.ts +3 -3
  250. package/src/separatedNonEmptyArrayParser.test.ts +117 -0
  251. package/src/separatedNonEmptyArrayParser.ts +61 -0
  252. package/src/sequenceBuffer.test.ts +9 -9
  253. package/src/sequenceBuffer.ts +4 -4
  254. package/src/sequenceTerminatedSequenceParser.test.ts +3 -5
  255. package/src/sequenceTerminatedSequenceParser.ts +4 -4
  256. package/src/sequenceUnparser.ts +2 -2
  257. package/src/skipToParser.ts +2 -2
  258. package/src/sliceBoundedParser.test.ts +4 -12
  259. package/src/sliceBoundedParser.ts +2 -2
  260. package/src/smali.ts +8 -3
  261. package/src/smaliParser.test.ts +377 -66
  262. package/src/smaliParser.test.ts.md +1635 -48
  263. package/src/smaliParser.test.ts.snap +0 -0
  264. package/src/smaliParser.ts +2751 -569
  265. package/src/stringFromAsyncIterable.ts +9 -0
  266. package/src/terminatedArrayParser.test.ts +11 -11
  267. package/src/terminatedArrayParser.ts +5 -7
  268. package/src/toAsyncIterator.ts +8 -8
  269. package/src/uint8Array.ts +2 -3
  270. package/src/unionParser.test.ts +22 -23
  271. package/src/unionParser.ts +6 -8
  272. package/src/unparser.test.ts +18 -34
  273. package/src/unparser.ts +13 -9
  274. package/src/unparserContext.ts +9 -13
  275. package/src/unparserError.ts +2 -1
  276. package/src/unparserImplementationInvariant.ts +1 -1
  277. package/src/unparserOutputCompanion.ts +1 -1
  278. package/src/zip.ts +2 -6
  279. package/src/zipParser.ts +10 -18
  280. package/src/zipUnparser.test.ts +1 -1
  281. package/src/zipUnparser.ts +52 -64
  282. package/tsconfig.json +7 -1
  283. package/xo.config.ts +15 -0
  284. package/.yarn/releases/yarn-4.5.3.cjs +0 -934
@@ -1,22 +1,145 @@
1
- import invariant from "invariant";
2
- import { dalvikBytecodeOperationCompanion } from "./dalvikBytecodeParser.js";
3
- import { isDalvikExecutableField, isDalvikExecutableMethod } from "./dalvikExecutable.js";
4
- import { createExactSequenceParser } from "./exactSequenceParser.js";
5
- import { cloneParser, setParserName } from "./parser.js";
6
- import { promiseCompose } from "./promiseCompose.js";
7
- import { createTupleParser } from "./tupleParser.js";
8
- import { createUnionParser } from "./unionParser.js";
9
- import { createArrayParser } from "./arrayParser.js";
10
- import { jsonNumberParser, jsonStringParser } from "./jsonParser.js";
11
- import { createNonEmptyArrayParser } from "./nonEmptyArrayParser.js";
12
- import { createOptionalParser } from "./optionalParser.js";
13
- import { createNegativeLookaheadParser } from "./negativeLookaheadParser.js";
14
- import { createSeparatedArrayParser } from "./separatedArrayParser.js";
15
- import { smaliMemberNameParser, smaliTypeDescriptorParser } from "./dalvikExecutableParser/stringSyntaxParser.js";
16
- import { createDisjunctionParser } from "./disjunctionParser.js";
17
- const smaliNewlinesParser = promiseCompose(createNonEmptyArrayParser(createExactSequenceParser('\n')), (_newlines) => undefined);
18
- const smaliOptionalIndentationParser = promiseCompose(createArrayParser(createExactSequenceParser(' ')), (_indentation) => undefined);
19
- const smaliCommentParser = promiseCompose(createTupleParser([
1
+ import invariant from 'invariant';
2
+ import { dalvikBytecodeOperationCompanion } from './dalvikBytecodeParser.js';
3
+ import { dalvikExecutableAccessFlagsDefault, dalvikExecutableFieldEquals, dalvikExecutableMethodEquals, isDalvikExecutableField, isDalvikExecutableMethod, } from './dalvikExecutable.js';
4
+ import { createExactSequenceParser } from './exactSequenceParser.js';
5
+ import { cloneParser, setParserName } from './parser.js';
6
+ import { promiseCompose } from './promiseCompose.js';
7
+ import { createTupleParser } from './tupleParser.js';
8
+ import { createUnionParser } from './unionParser.js';
9
+ import { createArrayParser } from './arrayParser.js';
10
+ import { jsonNumberParser, jsonStringParser } from './jsonParser.js';
11
+ import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
12
+ import { createOptionalParser } from './optionalParser.js';
13
+ import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
14
+ import { createSeparatedArrayParser } from './separatedArrayParser.js';
15
+ import { smaliMemberNameParser, smaliTypeDescriptorParser } from './dalvikExecutableParser/stringSyntaxParser.js';
16
+ import { createDisjunctionParser } from './disjunctionParser.js';
17
+ import { formatSizes } from './dalvikBytecodeParser/formatSizes.js';
18
+ import { operationFormats } from './dalvikBytecodeParser/operationFormats.js';
19
+ import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
20
+ import { parserCreatorCompose } from './parserCreatorCompose.js';
21
+ import { createElementParser } from './elementParser.js';
22
+ import { createParserAccessorParser } from './parserAccessorParser.js';
23
+ function shortyFromLongy(longy) {
24
+ if (longy.startsWith('[')) {
25
+ return 'L';
26
+ }
27
+ return longy.slice(0, 1);
28
+ }
29
+ function getOperationFormatSize(operation) {
30
+ if (operation.operation === 'packed-switch-payload') {
31
+ return (operation.branchOffsetIndices.length * 2) + 4;
32
+ }
33
+ if (operation.operation === 'sparse-switch-payload') {
34
+ return (operation.branchOffsetIndices.length * 4) + 2;
35
+ }
36
+ if (operation.operation === 'fill-array-data-payload') {
37
+ const dataSize = operation.data.length; // in bytes
38
+ const paddingSize = dataSize % 2; // 1 if odd, 0 if even
39
+ const totalBytes = 8 + dataSize + paddingSize; // header (8 bytes) + data + padding
40
+ return totalBytes / 2; // Convert to code units (1 code unit = 2 bytes)
41
+ }
42
+ const operationFormat = operationFormats[operation.operation];
43
+ invariant(operationFormat, 'Unknown operation format for "%s" (operation: %o)', operation.operation, operation);
44
+ const operationSize = formatSizes[operationFormat];
45
+ invariant(operationSize, 'Unknown operation size for format %s of operation %s', operationFormat, operation.operation);
46
+ return operationSize;
47
+ }
48
+ // Helper function to convert raw annotation element values to tagged encoded values
49
+ function convertToTaggedEncodedValue(wrappedValue) {
50
+ const { kind, value } = wrappedValue;
51
+ // Handle type descriptors
52
+ if (kind === 'type') {
53
+ if (Array.isArray(value)) {
54
+ // Array of type descriptors
55
+ return { type: 'array', value: value.map(v => ({ type: 'type', value: v })) };
56
+ }
57
+ // Single type descriptor
58
+ return { type: 'type', value };
59
+ }
60
+ // Handle string literals
61
+ if (kind === 'string') {
62
+ if (Array.isArray(value)) {
63
+ // Array of strings
64
+ return { type: 'array', value: value.map(v => ({ type: 'string', value: v })) };
65
+ }
66
+ // Single string
67
+ return { type: 'string', value };
68
+ }
69
+ // Handle enum values
70
+ if (kind === 'enum') {
71
+ if (Array.isArray(value)) {
72
+ // Array of enum values
73
+ return { type: 'array', value: value.map(v => ({ type: 'enum', value: v })) };
74
+ }
75
+ // Single enum value
76
+ return { type: 'enum', value };
77
+ }
78
+ // Handle raw values (everything else)
79
+ // Handle null
80
+ if (value === null) {
81
+ return { type: 'null', value: null };
82
+ }
83
+ // Handle boolean
84
+ if (typeof value === 'boolean') {
85
+ return { type: 'boolean', value };
86
+ }
87
+ // Handle numbers - we need to determine the type based on context
88
+ // For annotation elements from smali, we'll use 'int' as the default
89
+ if (typeof value === 'number') {
90
+ // Check if it's a float or integer
91
+ if (!Number.isInteger(value)) {
92
+ return { type: 'float', value };
93
+ }
94
+ // For integers, default to 'int' type
95
+ // The actual byte/short/int distinction would need more context
96
+ return { type: 'int', value };
97
+ }
98
+ // Handle bigint
99
+ if (typeof value === 'bigint') {
100
+ return { type: 'long', value };
101
+ }
102
+ // Handle DalvikExecutableField (including enums)
103
+ if (isDalvikExecutableField(value)) {
104
+ // Note: We can't distinguish between 'field' and 'enum' without more context
105
+ // Default to 'field' type - the context in smali might help distinguish later
106
+ return { type: 'field', value };
107
+ }
108
+ // Handle DalvikExecutableMethod
109
+ if (isDalvikExecutableMethod(value)) {
110
+ return { type: 'method', value };
111
+ }
112
+ // Handle DalvikExecutablePrototype (method type)
113
+ if (typeof value === 'object' && value !== null && 'returnType' in value && 'parameters' in value && 'shorty' in value) {
114
+ return { type: 'methodType', value: value };
115
+ }
116
+ // Handle annotations (subannotations)
117
+ if (typeof value === 'object' && value !== null && 'type' in value && 'elements' in value) {
118
+ const subannotation = value;
119
+ // Convert subannotation to DalvikExecutableAnnotation
120
+ const annotation = {
121
+ type: subannotation.type,
122
+ visibility: 'build', // Subannotations default to 'build' visibility
123
+ elements: subannotation.elements.map(element => ({
124
+ name: element.name,
125
+ value: convertToTaggedEncodedValue(element.value),
126
+ })),
127
+ };
128
+ return { type: 'annotation', value: annotation };
129
+ }
130
+ // Handle arrays
131
+ if (Array.isArray(value)) {
132
+ return { type: 'array', value: value.map(v => convertToTaggedEncodedValue({ kind: 'raw', value: v })) };
133
+ }
134
+ // Fallback - this shouldn't happen in well-formed smali
135
+ throw new Error(`Cannot convert value to tagged encoded value: ${JSON.stringify(value)}`);
136
+ }
137
+ const smaliNewlinesParser = promiseCompose(createNonEmptyArrayParser(createExactSequenceParser('\n')), _newlines => undefined);
138
+ const smaliSingleWhitespaceParser = createExactSequenceParser(' ');
139
+ const smaliWhitespaceParser = promiseCompose(createArrayParser(smaliSingleWhitespaceParser), _indentation => undefined);
140
+ const smaliSingleIndentationParser = createExactSequenceParser(' ');
141
+ const smaliIndentationParser = promiseCompose(createArrayParser(smaliSingleIndentationParser), _indentation => undefined);
142
+ export const smaliCommentParser = promiseCompose(createTupleParser([
20
143
  createExactSequenceParser('#'),
21
144
  (async (parserContext) => {
22
145
  const characters = [];
@@ -29,15 +152,28 @@ const smaliCommentParser = promiseCompose(createTupleParser([
29
152
  parserContext.skip(1);
30
153
  continue;
31
154
  }
155
+ parserContext.skip(1);
32
156
  break;
33
157
  }
34
158
  return characters.join('');
35
159
  }),
36
160
  ]), ([_hash, comment,]) => comment);
161
+ const smaliIndentedCommentParser = promiseCompose(createTupleParser([
162
+ createNonEmptyArrayParser(smaliSingleIndentationParser),
163
+ smaliCommentParser,
164
+ ]), ([_indentation, comment,]) => comment);
37
165
  const smaliCommentsOrNewlinesParser = promiseCompose(createArrayParser(createUnionParser([
38
166
  smaliNewlinesParser,
167
+ smaliIndentedCommentParser,
39
168
  smaliCommentParser,
40
- ])), (newlinesOrComments) => newlinesOrComments.filter((newlineOrComment) => typeof newlineOrComment === 'string'));
169
+ ])), newlinesOrComments => newlinesOrComments.filter((newlineOrComment) => typeof newlineOrComment === 'string'));
170
+ const smaliLineEndPraser = promiseCompose(createTupleParser([
171
+ createOptionalParser(smaliWhitespaceParser),
172
+ createUnionParser([
173
+ smaliNewlinesParser,
174
+ smaliCommentParser,
175
+ ]),
176
+ ]), ([_optionalWhitespace, newlineOrComment,]) => newlineOrComment);
41
177
  const smaliIdentifierParser = async (parserContext) => {
42
178
  const characters = [];
43
179
  while (true) {
@@ -58,54 +194,169 @@ const smaliIdentifierParser = async (parserContext) => {
58
194
  return characters.join('');
59
195
  };
60
196
  setParserName(smaliIdentifierParser, 'smaliIdentifierParser');
61
- const smaliQuotedStringParser = jsonStringParser;
197
+ const elementParser = createElementParser();
198
+ const smaliHexNumberParser = promiseCompose(createTupleParser([
199
+ createOptionalParser(createExactSequenceParser('-')),
200
+ createExactSequenceParser('0x'),
201
+ createArrayParser(parserCreatorCompose(() => elementParser, character => async (parserContext) => {
202
+ parserContext.invariant(((character >= '0' && character <= '9')
203
+ || (character >= 'a' && character <= 'f')
204
+ || (character >= 'A' && character <= 'F')), 'Expected "0" to "9", "a" to "f", "A" to "F", got "%s"', character);
205
+ return character;
206
+ })()),
207
+ createOptionalParser(createExactSequenceParser('L')),
208
+ ]), ([optionalMinus, _0x, valueCharacters, optionalL,]) => {
209
+ const value = valueCharacters.join('');
210
+ // If the 'L' suffix is present, use BigInt for long values
211
+ if (optionalL) {
212
+ const sign = optionalMinus ? -1n : 1n;
213
+ return sign * BigInt('0x' + value);
214
+ }
215
+ const sign = optionalMinus ? -1 : 1;
216
+ return sign * Number.parseInt(value, 16);
217
+ });
218
+ const smaliNumberParser = createUnionParser([
219
+ promiseCompose(createTupleParser([
220
+ createNegativeLookaheadParser(createUnionParser([
221
+ createExactSequenceParser('0x'),
222
+ createExactSequenceParser('-0x'),
223
+ ])),
224
+ jsonNumberParser,
225
+ createOptionalParser(createUnionParser([
226
+ createExactSequenceParser('f'),
227
+ createExactSequenceParser('F'),
228
+ ])),
229
+ ]), ([_not0x, number, optionalFloatSuffix,]) => {
230
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
231
+ // to match what would be stored in a DEX file
232
+ if (optionalFloatSuffix) {
233
+ const float32Array = new Float32Array(1);
234
+ float32Array[0] = number;
235
+ return float32Array[0];
236
+ }
237
+ return number;
238
+ }),
239
+ promiseCompose(smaliHexNumberParser, value => {
240
+ // For smaliNumberParser, we need to ensure we return a number
241
+ // BigInt values from hex parser should be converted if they fit in number range
242
+ if (typeof value === 'bigint') {
243
+ // This shouldn't happen in contexts where smaliNumberParser is used
244
+ // (registers, line numbers, etc) but if it does, convert to number
245
+ return Number(value);
246
+ }
247
+ return value;
248
+ }),
249
+ ]);
250
+ setParserName(smaliNumberParser, 'smaliNumberParser');
251
+ // Parser for field initial values that can include BigInt
252
+ const smaliFieldValueParser = createUnionParser([
253
+ promiseCompose(createTupleParser([
254
+ createNegativeLookaheadParser(createUnionParser([
255
+ createExactSequenceParser('0x'),
256
+ createExactSequenceParser('-0x'),
257
+ ])),
258
+ jsonNumberParser,
259
+ createOptionalParser(createUnionParser([
260
+ createExactSequenceParser('f'),
261
+ createExactSequenceParser('F'),
262
+ ])),
263
+ ]), ([_not0x, number, optionalFloatSuffix,]) => {
264
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
265
+ // to match what would be stored in a DEX file
266
+ if (optionalFloatSuffix) {
267
+ const float32Array = new Float32Array(1);
268
+ float32Array[0] = number;
269
+ return float32Array[0];
270
+ }
271
+ return number;
272
+ }),
273
+ smaliHexNumberParser,
274
+ ]);
275
+ const smaliQuotedStringParser = promiseCompose(jsonStringParser, string => string.replaceAll(String.raw `\'`, '\''));
276
+ // Parser for smali character literals (e.g., 'a', ':', '\'', '\\')
277
+ const smaliCharacterLiteralParser = promiseCompose(createTupleParser([
278
+ createExactSequenceParser('\''),
279
+ createDisjunctionParser([
280
+ // Handle escape sequences (must come before regular characters)
281
+ promiseCompose(createExactSequenceParser(String.raw `\\`), () => '\\'),
282
+ promiseCompose(createExactSequenceParser(String.raw `\'`), () => '\''),
283
+ promiseCompose(createExactSequenceParser(String.raw `\"`), () => '"'),
284
+ promiseCompose(createExactSequenceParser(String.raw `\n`), () => '\n'),
285
+ promiseCompose(createExactSequenceParser(String.raw `\r`), () => '\r'),
286
+ promiseCompose(createExactSequenceParser(String.raw `\t`), () => '\t'),
287
+ promiseCompose(createExactSequenceParser(String.raw `\b`), () => '\b'),
288
+ promiseCompose(createExactSequenceParser(String.raw `\f`), () => '\f'),
289
+ // Handle regular characters (not a single quote)
290
+ parserCreatorCompose(() => createElementParser(), character => async (parserContext) => {
291
+ parserContext.invariant(character !== '\'', 'Unexpected single quote');
292
+ return character;
293
+ })(),
294
+ ]),
295
+ createExactSequenceParser('\''),
296
+ ]), ([, character]) => character.charCodeAt(0));
297
+ setParserName(smaliCharacterLiteralParser, 'smaliCharacterLiteralParser');
298
+ // Parser that matches identifier continuation characters (letters, digits, $, -, _)
299
+ const smaliIdentifierContinuationParser = async (parserContext) => {
300
+ const character = await parserContext.peek(0);
301
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
302
+ invariant(character !== undefined, 'Unexpected end of input');
303
+ parserContext.invariant((character >= 'a' && character <= 'z')
304
+ || (character >= 'A' && character <= 'Z')
305
+ || (character >= '0' && character <= '9')
306
+ || character === '$'
307
+ || character === '-'
308
+ || character === '_', 'Expected identifier continuation character, got "%s"', character);
309
+ parserContext.skip(1);
310
+ return character;
311
+ };
312
+ setParserName(smaliIdentifierContinuationParser, 'smaliIdentifierContinuationParser');
313
+ // Helper to create an access flag parser with word boundary check
314
+ const createAccessFlagParser = (keyword) => promiseCompose(createTupleParser([
315
+ createExactSequenceParser(keyword),
316
+ createNegativeLookaheadParser(smaliIdentifierContinuationParser),
317
+ ]), ([flag]) => flag);
62
318
  const smaliAccessFlagsParser = promiseCompose(createSeparatedArrayParser(createUnionParser([
63
- createExactSequenceParser('public'),
64
- createExactSequenceParser('protected'),
65
- createExactSequenceParser('private'),
66
- createExactSequenceParser('final'),
67
- createExactSequenceParser('brigde'),
68
- createExactSequenceParser('synthetic'),
69
- createExactSequenceParser('varargs'),
70
- createExactSequenceParser('static'),
71
- createExactSequenceParser('constructor'),
72
- createExactSequenceParser('abstract'),
73
- // ... TODO
74
- ]), createExactSequenceParser(' ')), (accessFlagNames) => {
75
- const accessFlags = {
76
- public: false,
77
- private: false,
78
- protected: false,
79
- static: false,
80
- final: false,
81
- synchronized: false,
82
- volatile: false,
83
- bridge: false,
84
- transient: false,
85
- varargs: false,
86
- native: false,
87
- interface: false,
88
- abstract: false,
89
- strict: false,
90
- synthetic: false,
91
- annotation: false,
92
- enum: false,
93
- constructor: false,
94
- declaredSynchronized: false,
95
- };
319
+ createAccessFlagParser('public'),
320
+ createAccessFlagParser('protected'),
321
+ createAccessFlagParser('private'),
322
+ createAccessFlagParser('final'),
323
+ createAccessFlagParser('bridge'),
324
+ createAccessFlagParser('synthetic'),
325
+ createAccessFlagParser('varargs'),
326
+ createAccessFlagParser('static'),
327
+ createAccessFlagParser('constructor'),
328
+ createAccessFlagParser('abstract'),
329
+ createAccessFlagParser('native'),
330
+ createAccessFlagParser('volatile'),
331
+ createAccessFlagParser('transient'),
332
+ createAccessFlagParser('synchronized'),
333
+ createAccessFlagParser('declared-synchronized'),
334
+ createAccessFlagParser('strict'),
335
+ createAccessFlagParser('interface'),
336
+ createAccessFlagParser('annotation'),
337
+ createAccessFlagParser('enum'),
338
+ ]), smaliSingleWhitespaceParser), accessFlagNames => {
339
+ const accessFlags = dalvikExecutableAccessFlagsDefault();
96
340
  for (const accessFlagName of accessFlagNames) {
97
- accessFlags[accessFlagName] = true;
341
+ if (accessFlagName === 'declared-synchronized') {
342
+ accessFlags.declaredSynchronized = true;
343
+ }
344
+ else {
345
+ accessFlags[accessFlagName] = true;
346
+ }
98
347
  }
99
348
  return accessFlags;
100
349
  });
101
350
  const smaliClassDeclarationParser = promiseCompose(createTupleParser([
102
351
  smaliCommentsOrNewlinesParser,
103
352
  createExactSequenceParser('.class '),
104
- smaliAccessFlagsParser,
105
- createExactSequenceParser(' '),
353
+ createOptionalParser(promiseCompose(createTupleParser([
354
+ smaliAccessFlagsParser,
355
+ smaliSingleWhitespaceParser,
356
+ ]), ([accessFlags,]) => accessFlags)),
106
357
  smaliTypeDescriptorParser,
107
- createExactSequenceParser('\n'),
108
- ]), ([_commentsOrNewlines, _dotClass, accessFlags, _space, classPath, _newline,]) => ({
358
+ smaliLineEndPraser,
359
+ ]), ([_commentsOrNewlines, _dotClass, accessFlags = dalvikExecutableAccessFlagsDefault(), classPath,]) => ({
109
360
  accessFlags,
110
361
  class: classPath,
111
362
  }));
@@ -113,7 +364,7 @@ setParserName(smaliClassDeclarationParser, 'smaliClassDeclarationParser');
113
364
  const smaliSuperDeclarationParser = promiseCompose(createTupleParser([
114
365
  createExactSequenceParser('.super '),
115
366
  smaliTypeDescriptorParser,
116
- createExactSequenceParser('\n'),
367
+ smaliLineEndPraser,
117
368
  ]), ([_super, superclass, _newline,]) => ({
118
369
  superclass,
119
370
  }));
@@ -121,39 +372,192 @@ setParserName(smaliSuperDeclarationParser, 'smaliSuperDeclarationParser');
121
372
  const smaliInterfaceDeclarationParser = promiseCompose(createTupleParser([
122
373
  createExactSequenceParser('.implements '),
123
374
  smaliTypeDescriptorParser,
124
- createExactSequenceParser('\n'),
375
+ smaliLineEndPraser,
125
376
  ]), ([_interface, interface_, _newline,]) => interface_);
126
377
  setParserName(smaliInterfaceDeclarationParser, 'smaliInterfaceDeclarationParser');
127
378
  const smaliSourceDeclarationParser = promiseCompose(createTupleParser([
128
379
  createExactSequenceParser('.source '),
129
380
  smaliQuotedStringParser,
130
- createExactSequenceParser('\n'),
381
+ smaliLineEndPraser,
131
382
  ]), ([_source, sourceFile, _newline,]) => ({
132
383
  sourceFile,
133
384
  }));
385
+ const smaliEnumValueParser = promiseCompose(createTupleParser([
386
+ createExactSequenceParser('.enum '),
387
+ smaliTypeDescriptorParser,
388
+ createExactSequenceParser('->'),
389
+ smaliMemberNameParser,
390
+ createExactSequenceParser(':'),
391
+ smaliTypeDescriptorParser,
392
+ ]), ([_enum, classType, _arrow, fieldName, _colon, fieldType,]) => ({
393
+ class: classType,
394
+ type: fieldType,
395
+ name: fieldName,
396
+ }));
397
+ setParserName(smaliEnumValueParser, 'smaliEnumValueParser');
398
+ const smaliMethodPrototypeParser = promiseCompose(createTupleParser([
399
+ createExactSequenceParser('('),
400
+ createArrayParser(smaliTypeDescriptorParser),
401
+ createExactSequenceParser(')'),
402
+ smaliTypeDescriptorParser,
403
+ ]), ([_openParenthesis, parameters, _closeParenthesis, returnType,]) => ({
404
+ parameters,
405
+ returnType,
406
+ shorty: shortyFromLongy(returnType) + parameters.map(parameter => {
407
+ if (parameter === 'V') {
408
+ return '';
409
+ }
410
+ return shortyFromLongy(parameter);
411
+ }).join(''),
412
+ }));
413
+ setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
414
+ const smaliParametersMethodParser = promiseCompose(createTupleParser([
415
+ smaliTypeDescriptorParser,
416
+ createExactSequenceParser('->'),
417
+ smaliMemberNameParser,
418
+ smaliMethodPrototypeParser,
419
+ ]), ([classPath, _separator, methodName, prototype,]) => ({
420
+ class: classPath,
421
+ prototype,
422
+ name: methodName,
423
+ }));
424
+ setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
425
+ // Forward declaration to handle circular reference
426
+ let smaliSubannotationParser;
427
+ const smaliAnnotationElementParser = promiseCompose(createTupleParser([
428
+ smaliIndentationParser,
429
+ smaliIdentifierParser,
430
+ createExactSequenceParser(' = '),
431
+ createDisjunctionParser([
432
+ promiseCompose(createParserAccessorParser(() => smaliSubannotationParser), value => ({ kind: 'raw', value })),
433
+ promiseCompose(smaliEnumValueParser, value => ({ kind: 'enum', value })),
434
+ promiseCompose(smaliQuotedStringParser, value => ({ kind: 'string', value })),
435
+ promiseCompose(smaliParametersMethodParser, value => ({ kind: 'raw', value })),
436
+ promiseCompose(smaliTypeDescriptorParser, value => ({ kind: 'type', value })),
437
+ promiseCompose(smaliNumberParser, value => ({ kind: 'raw', value })),
438
+ promiseCompose(createExactSequenceParser('true'), () => ({ kind: 'raw', value: true })),
439
+ promiseCompose(createExactSequenceParser('false'), () => ({ kind: 'raw', value: false })),
440
+ promiseCompose(createExactSequenceParser('null'), () => ({ kind: 'raw', value: null })),
441
+ promiseCompose(createExactSequenceParser('{}'), () => ({ kind: 'raw', value: [] })),
442
+ promiseCompose(createTupleParser([
443
+ createExactSequenceParser('{\n'),
444
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
445
+ smaliIndentationParser,
446
+ smaliEnumValueParser,
447
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
448
+ smaliLineEndPraser,
449
+ smaliIndentationParser,
450
+ createExactSequenceParser('}'),
451
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'enum', value })),
452
+ promiseCompose(createTupleParser([
453
+ createExactSequenceParser('{\n'),
454
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
455
+ smaliIndentationParser,
456
+ smaliTypeDescriptorParser,
457
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
458
+ smaliLineEndPraser,
459
+ smaliIndentationParser,
460
+ createExactSequenceParser('}'),
461
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'type', value })),
462
+ promiseCompose(createTupleParser([
463
+ createExactSequenceParser('{\n'),
464
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
465
+ smaliIndentationParser,
466
+ smaliQuotedStringParser,
467
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
468
+ smaliLineEndPraser,
469
+ smaliIndentationParser,
470
+ createExactSequenceParser('}'),
471
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'string', value })),
472
+ promiseCompose(createTupleParser([
473
+ createExactSequenceParser('{\n'),
474
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
475
+ smaliIndentationParser,
476
+ smaliNumberParser,
477
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
478
+ smaliLineEndPraser,
479
+ smaliIndentationParser,
480
+ createExactSequenceParser('}'),
481
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'raw', value })),
482
+ ]),
483
+ smaliLineEndPraser,
484
+ ]), ([_indentation, name, _equalsSign, value, _newline,]) => ({
485
+ name,
486
+ value: value,
487
+ }));
488
+ setParserName(smaliAnnotationElementParser, 'smaliAnnotationElementParser');
489
+ // Now define the subannotation parser
490
+ smaliSubannotationParser = promiseCompose(createTupleParser([
491
+ createExactSequenceParser('.subannotation '),
492
+ smaliTypeDescriptorParser,
493
+ smaliLineEndPraser,
494
+ createArrayParser(smaliAnnotationElementParser),
495
+ smaliIndentationParser,
496
+ createExactSequenceParser('.end subannotation'),
497
+ ]), ([_subannotation, type, _newline, elements, _indentation, _endSubannotation,]) => ({
498
+ type,
499
+ elements,
500
+ }));
501
+ setParserName(smaliSubannotationParser, 'smaliSubannotationParser');
502
+ export const smaliAnnotationParser = promiseCompose(createTupleParser([
503
+ smaliIndentationParser,
504
+ createExactSequenceParser('.annotation '),
505
+ createUnionParser([
506
+ createExactSequenceParser('build'),
507
+ createExactSequenceParser('runtime'),
508
+ createExactSequenceParser('system'),
509
+ ]),
510
+ smaliSingleWhitespaceParser,
511
+ smaliTypeDescriptorParser,
512
+ smaliLineEndPraser,
513
+ createArrayParser(smaliAnnotationElementParser),
514
+ smaliIndentationParser,
515
+ createExactSequenceParser('.end annotation\n'),
516
+ ]), ([_indentation0, _annotation, visibility, _space, type, _newline, elements, _indentation1, _endAnnotation,]) => ({
517
+ type,
518
+ elements,
519
+ visibility,
520
+ }));
521
+ setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
134
522
  export const smaliFieldParser = promiseCompose(createTupleParser([
135
523
  createExactSequenceParser('.field '),
136
- smaliAccessFlagsParser,
137
- createExactSequenceParser(' '),
524
+ createOptionalParser(promiseCompose(createTupleParser([
525
+ smaliAccessFlagsParser,
526
+ smaliSingleWhitespaceParser,
527
+ ]), ([accessFlags, _space]) => accessFlags)),
138
528
  smaliMemberNameParser,
139
529
  createExactSequenceParser(':'),
140
530
  smaliTypeDescriptorParser,
141
- createExactSequenceParser('\n'),
142
- ]), ([_field, accessFlags, _space, name, _colon, type,]) => {
143
- return {
144
- accessFlags,
531
+ createOptionalParser(promiseCompose(createTupleParser([
532
+ createExactSequenceParser(' = '),
533
+ createUnionParser([
534
+ smaliCharacterLiteralParser,
535
+ smaliFieldValueParser,
536
+ smaliQuotedStringParser,
537
+ promiseCompose(createExactSequenceParser('true'), () => true),
538
+ promiseCompose(createExactSequenceParser('false'), () => false),
539
+ promiseCompose(createExactSequenceParser('null'), () => null),
540
+ ]),
541
+ ]), ([_equals, value,]) => value)),
542
+ smaliLineEndPraser,
543
+ createOptionalParser(promiseCompose(createTupleParser([
544
+ createSeparatedArrayParser(smaliAnnotationParser, smaliNewlinesParser),
545
+ createExactSequenceParser('.end field\n'),
546
+ ]), ([annotations, _endField,]) => annotations)),
547
+ ]), ([_field, accessFlags, name, _colon, type, initialValue, _newline, annotations,]) => ({
548
+ field: {
549
+ accessFlags: accessFlags ?? dalvikExecutableAccessFlagsDefault(),
145
550
  field: {
146
551
  class: 'FILLED_LATER',
147
552
  type,
148
553
  name,
149
554
  },
150
- };
151
- });
555
+ },
556
+ annotations: annotations ?? [],
557
+ ...(initialValue !== undefined ? { initialValue } : {}),
558
+ }));
152
559
  setParserName(smaliFieldParser, 'smaliFieldParser');
153
- const smaliFieldsParser = promiseCompose(createArrayParser(createDisjunctionParser([
154
- smaliFieldParser,
155
- smaliCommentsOrNewlinesParser,
156
- ])), (fieldsAndComments) => {
560
+ const smaliFieldsParser = promiseCompose(createSeparatedNonEmptyArrayParser(smaliFieldParser, smaliCommentsOrNewlinesParser), fieldsAndComments => {
157
561
  let type = 'instanceField';
158
562
  const staticFields = [];
159
563
  const instanceFields = [];
@@ -171,7 +575,8 @@ const smaliFieldsParser = promiseCompose(createArrayParser(createDisjunctionPars
171
575
  }
172
576
  invariant(typeof fieldOrComment === 'object', 'Expected field or comment');
173
577
  const field = fieldOrComment;
174
- if (type === 'staticField') {
578
+ if (type === 'staticField'
579
+ || field.field.accessFlags.static) {
175
580
  staticFields.push(field);
176
581
  continue;
177
582
  }
@@ -203,85 +608,28 @@ const smaliShortyReturnTypeParser = createUnionParser([
203
608
  smaliShortyFieldTypeParser,
204
609
  ]);
205
610
  setParserName(smaliShortyReturnTypeParser, 'smaliShortyReturnTypeParser');
206
- function shortyFromLongy(longy) {
207
- if (longy.startsWith('[')) {
208
- return 'L';
611
+ function shortyGetInsSize(shorty) {
612
+ let size = 0;
613
+ for (const char of shorty.slice(1)) {
614
+ if (char === 'J' || char === 'D') {
615
+ size += 2;
616
+ }
617
+ else if (char !== 'V') {
618
+ size += 1;
619
+ }
209
620
  }
210
- return longy.slice(0, 1);
621
+ return size;
211
622
  }
212
- const smaliMethodPrototypeParser = promiseCompose(createTupleParser([
213
- createExactSequenceParser('('),
214
- createArrayParser(smaliTypeDescriptorParser),
215
- createExactSequenceParser(')'),
216
- smaliTypeDescriptorParser,
217
- ]), ([_openParenthesis, parameters, _closeParenthesis, returnType,]) => ({
218
- parameters,
219
- returnType,
220
- shorty: shortyFromLongy(returnType) + parameters.map((parameter) => {
221
- if (parameter === 'V') {
222
- return '';
223
- }
224
- return shortyFromLongy(parameter);
225
- }).join(''),
226
- }));
227
- setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
228
623
  const smaliCodeRegistersParser = promiseCompose(createTupleParser([
229
624
  createExactSequenceParser(' .registers '),
230
- jsonNumberParser,
231
- createExactSequenceParser('\n'),
625
+ smaliNumberParser,
626
+ smaliLineEndPraser,
232
627
  ]), ([_registers, registers, _newline,]) => registers);
233
628
  setParserName(smaliCodeRegistersParser, 'smaliCodeRegistersParser');
234
- export const smaliAnnotationParser = promiseCompose(createTupleParser([
235
- smaliOptionalIndentationParser,
236
- createExactSequenceParser('.annotation '),
237
- createUnionParser([
238
- createExactSequenceParser('build'),
239
- createExactSequenceParser('runtime'),
240
- createExactSequenceParser('system'),
241
- ]),
242
- createExactSequenceParser(' '),
243
- smaliTypeDescriptorParser,
244
- createExactSequenceParser('\n'),
245
- smaliOptionalIndentationParser,
246
- createOptionalParser(promiseCompose(createTupleParser([
247
- createExactSequenceParser('value = '),
248
- createUnionParser([
249
- smaliTypeDescriptorParser,
250
- promiseCompose(createTupleParser([
251
- createExactSequenceParser('{\n'),
252
- createSeparatedArrayParser(promiseCompose(createTupleParser([
253
- smaliOptionalIndentationParser,
254
- smaliTypeDescriptorParser,
255
- ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
256
- createExactSequenceParser('\n'),
257
- smaliOptionalIndentationParser,
258
- createExactSequenceParser('}'),
259
- ]), ([_openBrace, value, _closeBrace,]) => value),
260
- promiseCompose(createTupleParser([
261
- createExactSequenceParser('{\n'),
262
- createSeparatedArrayParser(promiseCompose(createTupleParser([
263
- smaliOptionalIndentationParser,
264
- smaliQuotedStringParser,
265
- ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
266
- createExactSequenceParser('\n'),
267
- smaliOptionalIndentationParser,
268
- createExactSequenceParser('}'),
269
- ]), ([_openBrace, value, _closeBrace,]) => value),
270
- ]),
271
- createExactSequenceParser('\n'),
272
- ]), ([_indentation, value, _newline,]) => value)),
273
- smaliOptionalIndentationParser,
274
- createExactSequenceParser('.end annotation\n'),
275
- ]), ([_indentation0, _annotation, _accessFlags, _space, type, _newline, _indentation1, value, _endAnnotation,]) => ({
276
- type,
277
- value,
278
- // accessFlags,
279
- }));
280
- setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
281
629
  const smaliCodeLineParser = promiseCompose(createTupleParser([
282
630
  createExactSequenceParser(' .line '),
283
- jsonNumberParser,
284
- createExactSequenceParser('\n'),
631
+ smaliNumberParser,
632
+ smaliLineEndPraser,
285
633
  ]), ([_line, line, _newline,]) => line);
286
634
  setParserName(smaliCodeLineParser, 'smaliCodeLineParser');
287
635
  function isSmaliRegister(value) {
@@ -295,11 +643,11 @@ function isSmaliRegister(value) {
295
643
  const smaliParametersRegisterParser = promiseCompose(createUnionParser([
296
644
  createTupleParser([
297
645
  createExactSequenceParser('v'),
298
- jsonNumberParser,
646
+ smaliNumberParser,
299
647
  ]),
300
648
  createTupleParser([
301
649
  createExactSequenceParser('p'),
302
- jsonNumberParser,
650
+ smaliNumberParser,
303
651
  ]),
304
652
  ]), ([prefix, index,]) => ({
305
653
  prefix,
@@ -311,34 +659,133 @@ const smaliCodeLocalParser = promiseCompose(createTupleParser([
311
659
  smaliParametersRegisterParser,
312
660
  createOptionalParser(createTupleParser([
313
661
  createExactSequenceParser(','),
314
- createArrayParser(createExactSequenceParser(' ')),
315
- jsonStringParser,
662
+ smaliWhitespaceParser,
663
+ smaliQuotedStringParser,
316
664
  createExactSequenceParser(':'),
317
665
  smaliTypeDescriptorParser,
318
666
  ])),
319
- createExactSequenceParser('\n'),
320
- ]), ([_local, local, _newlocal,]) => local);
667
+ smaliLineEndPraser,
668
+ ]), ([_local, register, nameAndType,]) => ({
669
+ register,
670
+ name: nameAndType?.[2],
671
+ type: nameAndType?.[4],
672
+ }));
321
673
  setParserName(smaliCodeLocalParser, 'smaliCodeLocalParser');
674
+ const smaliCodeEndLocalParser = promiseCompose(createTupleParser([
675
+ createExactSequenceParser(' .end local '),
676
+ smaliParametersRegisterParser,
677
+ smaliLineEndPraser,
678
+ ]), ([_endLocal, register, _newline,]) => register);
679
+ setParserName(smaliCodeEndLocalParser, 'smaliCodeEndLocalParser');
680
+ const smaliCodeRestartLocalParser = promiseCompose(createTupleParser([
681
+ createExactSequenceParser(' .restart local '),
682
+ smaliParametersRegisterParser,
683
+ smaliLineEndPraser,
684
+ ]), ([_restartLocal, register, _newline,]) => register);
685
+ setParserName(smaliCodeRestartLocalParser, 'smaliCodeRestartLocalParser');
686
+ const smaliCodePrologueEndParser = promiseCompose(createTupleParser([
687
+ createExactSequenceParser(' .prologue'),
688
+ smaliLineEndPraser,
689
+ ]), () => true);
690
+ setParserName(smaliCodePrologueEndParser, 'smaliCodePrologueEndParser');
691
+ const smaliCodeEpilogueBeginParser = promiseCompose(createTupleParser([
692
+ createExactSequenceParser(' .epilogue'),
693
+ smaliLineEndPraser,
694
+ ]), () => true);
695
+ setParserName(smaliCodeEpilogueBeginParser, 'smaliCodeEpilogueBeginParser');
322
696
  export const smaliCodeParameterParser = promiseCompose(createTupleParser([
323
697
  createExactSequenceParser(' .param '),
324
698
  smaliParametersRegisterParser,
325
699
  createOptionalParser(createTupleParser([
326
700
  createExactSequenceParser(','),
327
- createArrayParser(createExactSequenceParser(' ')),
328
- jsonStringParser,
329
- createArrayParser(createExactSequenceParser(' ')),
701
+ smaliWhitespaceParser,
702
+ smaliQuotedStringParser,
703
+ smaliWhitespaceParser,
330
704
  ])),
331
- createOptionalParser(smaliCommentParser),
332
- createExactSequenceParser('\n'),
333
- ]), ([_param, parameter, _newparam, _commentOrNewline,]) => parameter);
705
+ createOptionalParser(smaliWhitespaceParser),
706
+ smaliCommentsOrNewlinesParser,
707
+ createOptionalParser(createTupleParser([
708
+ smaliAnnotationParser,
709
+ smaliIndentationParser,
710
+ createExactSequenceParser('.end param\n'),
711
+ ])),
712
+ ]), ([_parameter, register, optionalCommaAndString, _whitespace, _commentOrNewline, optionalAnnotation,]) => {
713
+ let annotation;
714
+ if (optionalAnnotation) {
715
+ annotation = optionalAnnotation[0];
716
+ }
717
+ return {
718
+ register,
719
+ name: optionalCommaAndString?.[2],
720
+ annotation,
721
+ };
722
+ });
334
723
  setParserName(smaliCodeParameterParser, 'smaliCodeParameterParser');
335
724
  const smaliCodeLabelParser = promiseCompose(createTupleParser([
336
- createExactSequenceParser(' :'),
725
+ createExactSequenceParser(':'),
337
726
  smaliIdentifierParser,
338
- createExactSequenceParser('\n'),
339
- ]), ([_label, label, _newlabel,]) => label);
727
+ ]), ([_label, label,]) => label);
340
728
  setParserName(smaliCodeLabelParser, 'smaliCodeLabelParser');
341
- const smaliParametersRegistersParser = promiseCompose(createTupleParser([
729
+ const smaliCodeLabelLineParser = promiseCompose(createTupleParser([
730
+ smaliIndentationParser,
731
+ smaliCodeLabelParser,
732
+ smaliLineEndPraser,
733
+ ]), ([_label, label, _newlabel,]) => label);
734
+ setParserName(smaliCodeLabelLineParser, 'smaliCodeLabelLineParser');
735
+ const smaliCatchDirectiveParser = promiseCompose(createTupleParser([
736
+ smaliIndentationParser,
737
+ createExactSequenceParser('.catch'),
738
+ createUnionParser([
739
+ promiseCompose(createExactSequenceParser('all'), () => undefined),
740
+ promiseCompose(createTupleParser([
741
+ createExactSequenceParser(' '),
742
+ smaliTypeDescriptorParser,
743
+ ]), ([_space, type,]) => type),
744
+ ]),
745
+ createExactSequenceParser(' {'),
746
+ smaliCodeLabelParser,
747
+ createExactSequenceParser(' .. '),
748
+ smaliCodeLabelParser,
749
+ createExactSequenceParser('} '),
750
+ smaliCodeLabelParser,
751
+ smaliLineEndPraser,
752
+ ]), ([_indentation, _catch, type, _openBrace, startLabel, _dots, endLabel, _closeBrace, handlerLabel, _newline,]) => ({
753
+ type,
754
+ startLabel,
755
+ endLabel,
756
+ handlerLabel,
757
+ }));
758
+ setParserName(smaliCatchDirectiveParser, 'smaliCatchDirectiveParser');
759
+ const smaliLabeledCatchDirectiveParser = promiseCompose(createTupleParser([
760
+ createArrayParser(smaliCodeLineParser),
761
+ createOptionalParser(smaliCodeLocalParser),
762
+ createArrayParser(smaliCodeLabelLineParser),
763
+ smaliCatchDirectiveParser,
764
+ ]), ([_lines, _local, labels, catchDirective,]) => ({
765
+ labels,
766
+ catchDirective,
767
+ }));
768
+ setParserName(smaliLabeledCatchDirectiveParser, 'smaliLabeledCatchDirectiveParser');
769
+ const smaliParametersRegisterRangeParser = promiseCompose(createTupleParser([
770
+ createExactSequenceParser('{'),
771
+ smaliParametersRegisterParser,
772
+ createExactSequenceParser(' .. '),
773
+ smaliParametersRegisterParser,
774
+ createExactSequenceParser('}'),
775
+ ]), ([_openBrace, startRegister, _dotDot, endRegister, _closeBrace,]) => {
776
+ invariant(startRegister.prefix === endRegister.prefix, 'Register range must use the same prefix');
777
+ invariant(startRegister.index <= endRegister.index, 'Register range start must be less than or equal to end');
778
+ const registers = [];
779
+ for (let i = startRegister.index; i <= endRegister.index; i++) {
780
+ registers.push({
781
+ prefix: startRegister.prefix,
782
+ index: i,
783
+ });
784
+ }
785
+ return registers;
786
+ });
787
+ setParserName(smaliParametersRegisterRangeParser, 'smaliParametersRegisterRangeParser');
788
+ const smaliParametersRegisterListParser = promiseCompose(createTupleParser([
342
789
  createExactSequenceParser('{'),
343
790
  createArrayParser(promiseCompose(createTupleParser([
344
791
  smaliParametersRegisterParser,
@@ -346,10 +793,16 @@ const smaliParametersRegistersParser = promiseCompose(createTupleParser([
346
793
  ]), ([parameter, _comma,]) => parameter)),
347
794
  createExactSequenceParser('}'),
348
795
  ]), ([_openBrace, parameters, _closeBrace,]) => parameters);
796
+ setParserName(smaliParametersRegisterListParser, 'smaliParametersRegisterListParser');
797
+ const smaliParametersRegistersParser = createUnionParser([
798
+ smaliParametersRegisterRangeParser,
799
+ smaliParametersRegisterListParser,
800
+ ]);
349
801
  setParserName(smaliParametersRegistersParser, 'smaliParametersRegistersParser');
350
802
  const smaliParametersStringParser = cloneParser(smaliQuotedStringParser);
351
803
  setParserName(smaliParametersStringParser, 'smaliParametersStringParser');
352
804
  const smaliParametersIntegerParser = promiseCompose(createTupleParser([
805
+ createOptionalParser(createExactSequenceParser('-')),
353
806
  createExactSequenceParser('0x'),
354
807
  async (parserContext) => {
355
808
  const characters = [];
@@ -368,12 +821,18 @@ const smaliParametersIntegerParser = promiseCompose(createTupleParser([
368
821
  }
369
822
  return characters.join('');
370
823
  },
371
- createOptionalParser(createExactSequenceParser('L')),
372
- ]), ([_0x, value, optionalL,]) => {
373
- if (optionalL) {
374
- return BigInt('0x' + value);
824
+ createOptionalParser(createUnionParser([
825
+ createExactSequenceParser('L'),
826
+ createExactSequenceParser('t'),
827
+ createExactSequenceParser('s'),
828
+ ])),
829
+ ]), ([optionalMinus, _0x, value, optionalSuffix,]) => {
830
+ if (optionalSuffix === 'L') {
831
+ const sign = optionalMinus ? -1n : 1n;
832
+ return sign * BigInt('0x' + value);
375
833
  }
376
- return parseInt(value, 16);
834
+ const sign = optionalMinus ? -1 : 1;
835
+ return sign * Number.parseInt(value, 16);
377
836
  });
378
837
  setParserName(smaliParametersIntegerParser, 'smaliParametersIntegerParser');
379
838
  const smaliParametersLabelParser = promiseCompose(createTupleParser([
@@ -383,17 +842,6 @@ const smaliParametersLabelParser = promiseCompose(createTupleParser([
383
842
  setParserName(smaliParametersLabelParser, 'smaliParametersLabelParser');
384
843
  const smaliParametersTypeParser = cloneParser(smaliTypeDescriptorParser);
385
844
  setParserName(smaliParametersTypeParser, 'smaliParametersTypeParser');
386
- const smaliParametersMethodParser = promiseCompose(createTupleParser([
387
- smaliTypeDescriptorParser,
388
- createExactSequenceParser('->'),
389
- smaliMemberNameParser,
390
- smaliMethodPrototypeParser,
391
- ]), ([classPath, _separator, methodName, prototype,]) => ({
392
- class: classPath,
393
- prototype,
394
- name: methodName,
395
- }));
396
- setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
397
845
  const smaliParametersFieldParser = promiseCompose(createTupleParser([
398
846
  smaliTypeDescriptorParser,
399
847
  createExactSequenceParser('->'),
@@ -412,6 +860,10 @@ const smaliCodeOperationParametersParser = createArrayParser(promiseCompose(crea
412
860
  smaliParametersRegistersParser,
413
861
  smaliParametersStringParser,
414
862
  smaliParametersIntegerParser,
863
+ promiseCompose(createTupleParser([
864
+ createNegativeLookaheadParser(smaliParametersIntegerParser),
865
+ smaliNumberParser,
866
+ ]), ([_notInteger, number]) => number),
415
867
  smaliParametersLabelParser,
416
868
  promiseCompose(createTupleParser([
417
869
  createNegativeLookaheadParser(smaliParametersMethodParser),
@@ -425,44 +877,134 @@ const smaliCodeOperationParametersParser = createArrayParser(promiseCompose(crea
425
877
  ]), ([parameter, _comma,]) => parameter));
426
878
  setParserName(smaliCodeOperationParametersParser, 'smaliCodeOperationParametersParser');
427
879
  const smaliCodeOperationNameParser = promiseCompose(createTupleParser([
428
- promiseCompose(createSeparatedArrayParser(smaliIdentifierParser, createExactSequenceParser('-')), (parts) => parts.join('-')),
880
+ promiseCompose(createSeparatedArrayParser(smaliIdentifierParser, createExactSequenceParser('-')), parts => parts.join('-')),
429
881
  createOptionalParser(promiseCompose(createTupleParser([
430
882
  createExactSequenceParser('/'),
431
883
  smaliIdentifierParser,
432
884
  ]), ([slash, name]) => slash + name)),
433
885
  ]), ([name, optionalSlashName,]) => name + (optionalSlashName || ''));
434
886
  setParserName(smaliCodeOperationNameParser, 'smaliCodeOperationNameParser');
435
- const smaliCodeOperationParser = promiseCompose(createTupleParser([
436
- createExactSequenceParser(' '),
887
+ const smaliOneLineCodeOperationParser = promiseCompose(createTupleParser([
888
+ smaliSingleIndentationParser,
437
889
  smaliCodeOperationNameParser,
438
890
  promiseCompose(createOptionalParser(createTupleParser([
439
- createExactSequenceParser(' '),
891
+ smaliSingleWhitespaceParser,
440
892
  smaliCodeOperationParametersParser,
441
- ])), (undefinedOrParameters) => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1]),
442
- createExactSequenceParser('\n'),
893
+ ])), undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1]),
894
+ smaliLineEndPraser,
443
895
  ]), ([_indent, operation, parameters, _newline,]) => ({
444
896
  operation,
445
897
  parameters,
446
898
  }));
447
- setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
448
- const operationsWithTypeArgument = new Set([
449
- 'new-instance',
450
- 'check-cast',
451
- 'instance-of',
899
+ setParserName(smaliOneLineCodeOperationParser, 'smaliOneLineCodeOperationParser');
900
+ const createMultilineSmaliCodeOperationLabelsBodyParser = operationName => promiseCompose(createTupleParser([
901
+ createArrayParser(promiseCompose(createTupleParser([
902
+ smaliIndentationParser,
903
+ smaliCodeLabelParser,
904
+ smaliLineEndPraser,
905
+ ]), ([_indentation, label, _newline,]) => label)),
906
+ smaliIndentationParser,
907
+ createExactSequenceParser('.end '),
908
+ createExactSequenceParser(operationName),
909
+ smaliLineEndPraser,
910
+ ]), ([labels,]) => labels);
911
+ const createMultilineSmaliCodeOperationLabelMapBodyParser = operationName => promiseCompose(createTupleParser([
912
+ createArrayParser(promiseCompose(createTupleParser([
913
+ smaliIndentationParser,
914
+ smaliParametersIntegerParser,
915
+ createExactSequenceParser(' -> '),
916
+ smaliCodeLabelParser,
917
+ smaliLineEndPraser,
918
+ ]), ([_indentation, key, _arrow, label, _newline,]) => [
919
+ key,
920
+ label,
921
+ ])),
922
+ smaliIndentationParser,
923
+ createExactSequenceParser('.end '),
924
+ createExactSequenceParser(operationName),
925
+ smaliLineEndPraser,
926
+ ]), ([labels,]) => labels);
927
+ const createMultilineSmaliCodeOperationIntegersBodyParser = operationName => promiseCompose(createTupleParser([
928
+ createArrayParser(promiseCompose(createTupleParser([
929
+ smaliIndentationParser,
930
+ smaliParametersIntegerParser,
931
+ smaliLineEndPraser,
932
+ ]), ([_indentation, key,]) => key)),
933
+ smaliIndentationParser,
934
+ createExactSequenceParser('.end '),
935
+ createExactSequenceParser(operationName),
936
+ smaliLineEndPraser,
937
+ ]), ([labels,]) => labels);
938
+ const createMultilineSmaliCodeOperationBodyParser = operationName => createUnionParser([
939
+ createMultilineSmaliCodeOperationLabelsBodyParser(operationName),
940
+ createMultilineSmaliCodeOperationIntegersBodyParser(operationName),
941
+ createMultilineSmaliCodeOperationLabelMapBodyParser(operationName),
452
942
  ]);
453
- const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
454
- createArrayParser(smaliCodeLineParser),
455
- createOptionalParser(smaliCodeLocalParser),
456
- createArrayParser(smaliCodeLabelParser),
457
- smaliCodeOperationParser,
458
- ]), ([_lines, _local, _labels, operation,]) => {
943
+ const smaliMultilineCodeOperationParser = parserCreatorCompose(() => promiseCompose(createTupleParser([
944
+ smaliSingleIndentationParser,
945
+ createExactSequenceParser('.'),
946
+ smaliCodeOperationNameParser,
947
+ promiseCompose(createOptionalParser(createTupleParser([
948
+ smaliSingleWhitespaceParser,
949
+ smaliCodeOperationParametersParser,
950
+ ])), undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1]),
951
+ smaliLineEndPraser,
952
+ ]), ([_indent, _dot, operation, parameters, _newline,]) => ({
953
+ operation,
954
+ parameters,
955
+ })), ({ operation, parameters }) => promiseCompose(createMultilineSmaliCodeOperationBodyParser(operation), body => ({
956
+ operation,
957
+ parameters,
958
+ body,
959
+ })))();
960
+ setParserName(smaliMultilineCodeOperationParser, 'smaliMultilineCodeOperationParser');
961
+ const smaliLooseCodeOperationParser = createUnionParser([
962
+ smaliOneLineCodeOperationParser,
963
+ smaliMultilineCodeOperationParser,
964
+ ]);
965
+ setParserName(smaliLooseCodeOperationParser, 'smaliLooseCodeOperationParser');
966
+ export const smaliCodeOperationParser = promiseCompose(smaliLooseCodeOperationParser, operation => {
459
967
  const operation_ = {
460
968
  operation: operation.operation,
461
- // line,
969
+ // Line,
462
970
  // local,
463
- // label,
464
971
  // parameters: (operation as any).parameters,
465
972
  }; // TODO
973
+ if ('body' in operation) {
974
+ const operationDexName = payloadOperationSmaliNameToDexName.get(operation.operation);
975
+ invariant(operationDexName, 'Unknown payload operation for %s', operation.operation);
976
+ operation_.operation = operationDexName;
977
+ if (operationDexName === 'sparse-switch-payload') {
978
+ const map = new Map(operation.body);
979
+ operation_.keys = [...map.keys()];
980
+ operation_.branchLabels = [...map.values()];
981
+ }
982
+ else if (operationDexName === 'fill-array-data-payload') {
983
+ // Get elementWidth from parameters (first parameter)
984
+ const elementWidth = operation.parameters[0];
985
+ invariant(typeof elementWidth === 'number', 'Expected elementWidth to be a number');
986
+ operation_.elementWidth = elementWidth;
987
+ // Convert integer values to bytes (little-endian)
988
+ const values = operation.body;
989
+ const data = [];
990
+ for (const value of values) {
991
+ // Use BigInt for bitwise operations to preserve full precision
992
+ const bigIntValue = typeof value === 'bigint' ? value : BigInt(value);
993
+ // Convert to bytes based on elementWidth (little-endian)
994
+ for (let i = 0; i < elementWidth; i++) {
995
+ data.push(Number((bigIntValue >> BigInt(i * 8)) & 0xffn));
996
+ }
997
+ }
998
+ operation_.data = data;
999
+ }
1000
+ else {
1001
+ operation_.branchLabels = operation.body;
1002
+ }
1003
+ }
1004
+ const hasRegisters = Array.isArray(operation.parameters.at(0));
1005
+ if (hasRegisters) {
1006
+ operation_.registers = [];
1007
+ }
466
1008
  for (const parameter of operation.parameters.flat() ?? []) {
467
1009
  if (isDalvikExecutableMethod(parameter)) {
468
1010
  operation_.method = parameter;
@@ -473,9 +1015,7 @@ const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
473
1015
  continue;
474
1016
  }
475
1017
  if (isSmaliRegister(parameter)) {
476
- if (!operation_.registers) {
477
- operation_.registers = [];
478
- }
1018
+ operation_.registers ||= [];
479
1019
  operation_.registers.push(parameter);
480
1020
  continue;
481
1021
  }
@@ -484,97 +1024,678 @@ const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
484
1024
  operation_.type = parameter;
485
1025
  continue;
486
1026
  }
1027
+ if (operationsWithBranchLabelArgument.has(operation_.operation)) {
1028
+ operation_.branchLabel = parameter;
1029
+ continue;
1030
+ }
487
1031
  operation_.string = parameter;
488
1032
  continue;
489
1033
  }
490
1034
  if (typeof parameter === 'number' || typeof parameter === 'bigint') {
491
- operation_.value = parameter;
1035
+ // Skip for fill-array-data-payload, already handled in body processing
1036
+ if (operation_.operation === 'fill-array-data-payload') {
1037
+ continue;
1038
+ }
1039
+ // Const-wide operations always use bigint values
1040
+ if (operationsWithBigintValue.has(operation_.operation)) {
1041
+ operation_.value = typeof parameter === 'number' ? BigInt(parameter) : parameter;
1042
+ }
1043
+ else {
1044
+ operation_.value = parameter;
1045
+ }
492
1046
  continue;
493
1047
  }
494
1048
  invariant(false, 'TODO: parameter: %s, operation: %s', JSON.stringify(parameter), JSON.stringify(operation));
495
1049
  }
496
- if (operation_.registers) {
497
- operation_.registers = operation_.registers.reverse();
498
- }
499
1050
  return operation_;
500
1051
  });
501
1052
  setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
1053
+ const operationsWithTypeArgument = new Set([
1054
+ 'new-instance',
1055
+ 'new-array',
1056
+ 'filled-new-array',
1057
+ 'filled-new-array/range',
1058
+ 'check-cast',
1059
+ 'instance-of',
1060
+ 'const-class',
1061
+ ]);
1062
+ const operationsWithBigintValue = new Set([
1063
+ 'const-wide/16',
1064
+ 'const-wide/32',
1065
+ 'const-wide',
1066
+ ]);
1067
+ const operationsWithBranchLabelArgument = new Set([
1068
+ 'goto',
1069
+ 'goto/16',
1070
+ 'goto/32',
1071
+ 'if-eq',
1072
+ 'if-ne',
1073
+ 'if-lt',
1074
+ 'if-ge',
1075
+ 'if-gt',
1076
+ 'if-le',
1077
+ 'if-eqz',
1078
+ 'if-nez',
1079
+ 'if-ltz',
1080
+ 'if-gez',
1081
+ 'if-gtz',
1082
+ 'if-lez',
1083
+ 'packed-switch',
1084
+ 'sparse-switch',
1085
+ 'fill-array-data',
1086
+ ]);
1087
+ const payloadOperationSmaliNameToDexName = new Map(Object.entries({
1088
+ 'packed-switch': 'packed-switch-payload',
1089
+ 'sparse-switch': 'sparse-switch-payload',
1090
+ 'array-data': 'fill-array-data-payload',
1091
+ }));
1092
+ function isOperationWithLabels(value) {
1093
+ return (typeof value === 'object'
1094
+ && value !== null
1095
+ && 'labels' in value
1096
+ && value.labels instanceof Set);
1097
+ }
1098
+ const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
1099
+ createArrayParser(smaliCodeLineParser),
1100
+ createOptionalParser(smaliCodeLocalParser),
1101
+ createOptionalParser(smaliCodeEndLocalParser),
1102
+ createOptionalParser(smaliCodeRestartLocalParser),
1103
+ createOptionalParser(smaliCodePrologueEndParser),
1104
+ createOptionalParser(smaliCodeEpilogueBeginParser),
1105
+ createArrayParser(smaliCodeLabelLineParser),
1106
+ smaliCodeOperationParser,
1107
+ ]), ([lines, local, endLocal, restartLocal, prologueEnd, epilogueBegin, labels, operation,]) => {
1108
+ const debugDirectives = [];
1109
+ for (const line of lines) {
1110
+ debugDirectives.push({ type: 'line', line });
1111
+ }
1112
+ if (local) {
1113
+ debugDirectives.push({ type: 'startLocal', local });
1114
+ }
1115
+ if (endLocal) {
1116
+ debugDirectives.push({ type: 'endLocal', register: endLocal });
1117
+ }
1118
+ if (restartLocal) {
1119
+ debugDirectives.push({ type: 'restartLocal', register: restartLocal });
1120
+ }
1121
+ if (prologueEnd) {
1122
+ debugDirectives.push({ type: 'setPrologueEnd' });
1123
+ }
1124
+ if (epilogueBegin) {
1125
+ debugDirectives.push({ type: 'setEpilogueBegin' });
1126
+ }
1127
+ return {
1128
+ debugDirectives,
1129
+ labels,
1130
+ operation,
1131
+ };
1132
+ });
1133
+ setParserName(smaliAnnotatedCodeOperationParser, 'smaliAnnotatedCodeOperationParser');
502
1134
  const smaliExecutableCodeParser = promiseCompose(createTupleParser([
503
- smaliCodeRegistersParser,
1135
+ createOptionalParser(smaliCodeRegistersParser),
504
1136
  createArrayParser(smaliAnnotationParser),
505
- createOptionalParser(smaliCodeParameterParser),
1137
+ createArrayParser(smaliCodeParameterParser),
1138
+ createOptionalParser(smaliCommentsOrNewlinesParser),
1139
+ createSeparatedArrayParser(smaliAnnotationParser, smaliCommentsOrNewlinesParser),
506
1140
  createArrayParser(promiseCompose(createTupleParser([
507
1141
  createOptionalParser(smaliCommentsOrNewlinesParser),
508
- smaliAnnotatedCodeOperationParser,
509
- ]), ([_commentsOrNewlines, operation,]) => operation)),
510
- ]), ([registersSize, _annotations, _parameters, instructions,]) => {
1142
+ createDisjunctionParser([
1143
+ smaliIndentedCommentParser,
1144
+ smaliAnnotatedCodeOperationParser,
1145
+ smaliLabeledCatchDirectiveParser,
1146
+ ]),
1147
+ ]), ([_commentsOrNewlinesParser, operation,]) => operation)),
1148
+ ]), ([registersSize, annotations1, parameters, _leadingCommentsOrNewlines, annotations2, instructionsAndCatchDirectives,]) => {
1149
+ const annotations = [...annotations1, ...annotations2];
1150
+ const instructions = [];
1151
+ const annotatedOperations = [];
1152
+ const catchDirectives = [];
1153
+ const catchDirectiveLabels = new Map();
1154
+ for (const item of instructionsAndCatchDirectives) {
1155
+ if (item && typeof item === 'object') {
1156
+ if ('labels' in item && 'catchDirective' in item) {
1157
+ // This is a SmaliLabeledCatchDirective
1158
+ const labeledCatch = item;
1159
+ catchDirectives.push(labeledCatch.catchDirective);
1160
+ catchDirectiveLabels.set(labeledCatch.catchDirective, labeledCatch.labels);
1161
+ }
1162
+ else if ('type' in item && 'startLabel' in item && 'endLabel' in item && 'handlerLabel' in item) {
1163
+ // This is a bare SmaliCatchDirective (shouldn't happen with current parser structure)
1164
+ catchDirectives.push(item);
1165
+ }
1166
+ else if ('debugDirectives' in item && 'labels' in item && 'operation' in item) {
1167
+ // This is a SmaliAnnotatedCodeOperation
1168
+ const annotated = item;
1169
+ annotatedOperations.push(annotated);
1170
+ const operation = annotated.operation;
1171
+ if (annotated.labels.length > 0) {
1172
+ operation.labels = new Set(annotated.labels);
1173
+ }
1174
+ instructions.push(operation);
1175
+ }
1176
+ else if (item !== undefined) {
1177
+ // This is a SmaliCodeOperation (fallback)
1178
+ instructions.push(item);
1179
+ }
1180
+ }
1181
+ }
1182
+ if (registersSize === undefined
1183
+ && parameters !== undefined) {
1184
+ registersSize = parameters.length;
1185
+ }
1186
+ invariant(registersSize !== undefined, 'Expected registers size to be defined');
1187
+ for (const [operationIndex, operation] of instructions.entries()) {
1188
+ if ('branchLabel' in operation
1189
+ && typeof operation.branchLabel === 'string') {
1190
+ for (const [targetOperationIndex, targetOperation] of instructions.entries()) {
1191
+ if (!isOperationWithLabels(targetOperation)) {
1192
+ continue;
1193
+ }
1194
+ if (!targetOperation.labels.has(operation.branchLabel)) {
1195
+ continue;
1196
+ }
1197
+ delete operation.branchLabel;
1198
+ operation.branchOffsetIndex = targetOperationIndex - operationIndex;
1199
+ break;
1200
+ }
1201
+ }
1202
+ if ('branchLabels' in operation
1203
+ && typeof operation.branchLabels === 'object'
1204
+ && Array.isArray(operation.branchLabels)) {
1205
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => ('branchOffsetIndex' in sourceOperation
1206
+ && typeof sourceOperation.branchOffsetIndex === 'number'
1207
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex));
1208
+ invariant(sourceOperations.length === 1, 'Expected exactly one source operation to point to %s', JSON.stringify(operation));
1209
+ const [sourceOperation] = sourceOperations;
1210
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
1211
+ operation.branchOffsetIndices = operation.branchLabels.map((branchLabel) => {
1212
+ for (const [targetOperationIndex, targetOperation] of instructions.entries()) {
1213
+ if (!isOperationWithLabels(targetOperation)) {
1214
+ continue;
1215
+ }
1216
+ if (!targetOperation.labels.has(branchLabel)) {
1217
+ continue;
1218
+ }
1219
+ return targetOperationIndex - sourceOperationIndex;
1220
+ }
1221
+ invariant(false, 'Expected to find branch label %s', branchLabel);
1222
+ });
1223
+ delete operation.branchLabels;
1224
+ }
1225
+ }
1226
+ const branchOffsetByBranchOffsetIndex = new Map();
1227
+ let operationOffset = 0;
1228
+ for (const [operationIndex, operation] of instructions.entries()) {
1229
+ const operationSize = getOperationFormatSize(operation);
1230
+ branchOffsetByBranchOffsetIndex.set(operationIndex, operationOffset);
1231
+ operationOffset += operationSize;
1232
+ }
1233
+ for (const [operationIndex, operation] of instructions.entries()) {
1234
+ if ('branchOffsetIndex' in operation
1235
+ && typeof operation.branchOffsetIndex === 'number') {
1236
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(operationIndex + operation.branchOffsetIndex);
1237
+ invariant(operationOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', operation.branchOffsetIndex);
1238
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(operationIndex);
1239
+ invariant(branchOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', operationIndex);
1240
+ operation.branchOffset = operationOffset - branchOffset;
1241
+ }
1242
+ if ('branchOffsetIndices' in operation
1243
+ && typeof operation.branchOffsetIndices === 'object'
1244
+ && Array.isArray(operation.branchOffsetIndices)) {
1245
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => ('branchOffsetIndex' in sourceOperation
1246
+ && typeof sourceOperation.branchOffsetIndex === 'number'
1247
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex));
1248
+ invariant(sourceOperations.length === 1, 'Expected exactly one source operation to point to %s', JSON.stringify(operation));
1249
+ const [sourceOperation] = sourceOperations;
1250
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
1251
+ operation.branchOffsets = operation.branchOffsetIndices.map((branchOffsetIndex) => {
1252
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex + branchOffsetIndex);
1253
+ invariant(operationOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', sourceOperationIndex + branchOffsetIndex);
1254
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex);
1255
+ invariant(branchOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', sourceOperationIndex);
1256
+ return operationOffset - branchOffset;
1257
+ });
1258
+ }
1259
+ }
1260
+ // Build label-to-index mapping
1261
+ // Labels attached to instructions map to that instruction's index
1262
+ // Labels before catch directives should map to the position they mark
1263
+ const labelToIndexMap = new Map();
1264
+ // First, map labels from instructions
1265
+ for (const [operationIndex, operation] of instructions.entries()) {
1266
+ if (operation
1267
+ && typeof operation === 'object'
1268
+ && 'labels' in operation
1269
+ && operation.labels instanceof Set) {
1270
+ for (const label of operation.labels) {
1271
+ labelToIndexMap.set(label, operationIndex);
1272
+ }
1273
+ }
1274
+ }
1275
+ // Now handle labels from catch directives
1276
+ // We need to figure out where each catch directive appears in the original sequence
1277
+ let instructionArrayIndex = 0;
1278
+ for (let i = 0; i < instructionsAndCatchDirectives.length; i++) {
1279
+ const item = instructionsAndCatchDirectives[i];
1280
+ if (item && typeof item === 'object' && 'labels' in item && 'catchDirective' in item) {
1281
+ // This is a catch directive with labels
1282
+ // These labels should map to the current instruction index
1283
+ // (which is where the next instruction would be)
1284
+ const labeledCatch = item;
1285
+ for (const label of labeledCatch.labels) {
1286
+ labelToIndexMap.set(label, instructionArrayIndex);
1287
+ }
1288
+ }
1289
+ else if (item !== undefined && !(item && typeof item === 'object' && 'type' in item && 'startLabel' in item)) {
1290
+ // This is an instruction, increment the counter
1291
+ instructionArrayIndex++;
1292
+ }
1293
+ }
1294
+ // Build tries array from catch directives
1295
+ const triesByRange = new Map();
1296
+ for (const catchDirective of catchDirectives) {
1297
+ // Find the start and end instruction indices
1298
+ const startIndex = labelToIndexMap.get(catchDirective.startLabel);
1299
+ const endIndex = labelToIndexMap.get(catchDirective.endLabel);
1300
+ const handlerIndex = labelToIndexMap.get(catchDirective.handlerLabel);
1301
+ invariant(startIndex !== undefined, 'Expected to find start label %s', catchDirective.startLabel);
1302
+ invariant(endIndex !== undefined, 'Expected to find end label %s', catchDirective.endLabel);
1303
+ invariant(handlerIndex !== undefined, 'Expected to find handler label %s', catchDirective.handlerLabel);
1304
+ const startAddress = branchOffsetByBranchOffsetIndex.get(startIndex);
1305
+ const endAddress = branchOffsetByBranchOffsetIndex.get(endIndex);
1306
+ const handlerAddress = branchOffsetByBranchOffsetIndex.get(handlerIndex);
1307
+ invariant(startAddress !== undefined, 'Expected start address for index %s', startIndex);
1308
+ invariant(endAddress !== undefined, 'Expected end address for index %s', endIndex);
1309
+ invariant(handlerAddress !== undefined, 'Expected handler address for index %s', handlerIndex);
1310
+ const instructionCount = endAddress - startAddress;
1311
+ const rangeKey = `${startAddress}-${instructionCount}`;
1312
+ let tryEntry = triesByRange.get(rangeKey);
1313
+ if (!tryEntry) {
1314
+ tryEntry = {
1315
+ startAddress,
1316
+ instructionCount,
1317
+ handlers: [],
1318
+ catchAllAddress: undefined,
1319
+ };
1320
+ triesByRange.set(rangeKey, tryEntry);
1321
+ }
1322
+ if (catchDirective.type === undefined) {
1323
+ // .catchall
1324
+ tryEntry.catchAllAddress = handlerAddress;
1325
+ }
1326
+ else {
1327
+ // .catch Type
1328
+ tryEntry.handlers.push({
1329
+ type: catchDirective.type,
1330
+ address: handlerAddress,
1331
+ });
1332
+ }
1333
+ }
1334
+ const tries = Array.from(triesByRange.values()).map(tryEntry => ({
1335
+ startAddress: tryEntry.startAddress,
1336
+ instructionCount: tryEntry.instructionCount,
1337
+ handler: {
1338
+ handlers: tryEntry.handlers,
1339
+ catchAllAddress: tryEntry.catchAllAddress,
1340
+ },
1341
+ }));
1342
+ for (const operation of instructions) {
1343
+ delete operation.labels;
1344
+ delete operation.branchOffsetIndex;
1345
+ delete operation.branchOffsetIndices;
1346
+ }
1347
+ let debugInfo;
1348
+ const debugBytecode = [];
1349
+ let currentLine;
1350
+ let lineStart;
1351
+ // Helper to convert SmaliRegister to register number
1352
+ const getRegisterNum = (register) => {
1353
+ // In Smali, 'v' registers start from 0, 'p' registers are parameters
1354
+ // For now, we'll just use the index. The actual conversion may need
1355
+ // to account for registersSize and parameter mapping.
1356
+ if (register.prefix === 'v') {
1357
+ return register.index;
1358
+ }
1359
+ else {
1360
+ // 'p' registers: need to map to actual register numbers
1361
+ // p0 starts at (registersSize - insSize)
1362
+ // For now, just use the index as we don't have accurate insSize
1363
+ return register.index;
1364
+ }
1365
+ };
1366
+ // Helper to emit accumulated line/address changes as special opcode or explicit opcodes
1367
+ const emitPendingChanges = (lineDiff, addressDiff) => {
1368
+ // Special opcode formula: opcode = 0x0A + (line_diff + 4) + 15 * addr_diff
1369
+ // Valid when: line_diff ∈ [-4, 10] and addr_diff ∈ [0, 17] and result ≤ 0xFF
1370
+ if (lineDiff >= -4 && lineDiff <= 10 && addressDiff >= 0) {
1371
+ const adjusted = (lineDiff + 4) + 15 * addressDiff;
1372
+ const opcode = 0x0A + adjusted;
1373
+ if (opcode <= 0xFF) {
1374
+ debugBytecode.push({
1375
+ type: 'special',
1376
+ value: opcode,
1377
+ });
1378
+ return;
1379
+ }
1380
+ }
1381
+ // Fall back to explicit opcodes
1382
+ if (addressDiff > 0) {
1383
+ debugBytecode.push({
1384
+ type: 'advancePc',
1385
+ addressDiff,
1386
+ });
1387
+ }
1388
+ if (lineDiff !== 0) {
1389
+ debugBytecode.push({
1390
+ type: 'advanceLine',
1391
+ lineDiff,
1392
+ });
1393
+ }
1394
+ };
1395
+ let accumulatedAddressDiff = 0;
1396
+ let accumulatedLineDiff = 0;
1397
+ let lastAddress = 0;
1398
+ let hasEmittedFirstDebugEvent = false;
1399
+ for (let i = 0; i < annotatedOperations.length; i++) {
1400
+ const annotated = annotatedOperations[i];
1401
+ const operationAddress = branchOffsetByBranchOffsetIndex.get(i) ?? 0;
1402
+ // Accumulate address difference (only after we've started tracking)
1403
+ if (i > 0 && hasEmittedFirstDebugEvent) {
1404
+ accumulatedAddressDiff += operationAddress - lastAddress;
1405
+ }
1406
+ lastAddress = operationAddress;
1407
+ // Process line directives first to update accumulatedLineDiff
1408
+ for (const directive of annotated.debugDirectives) {
1409
+ if (directive.type === 'line') {
1410
+ if (lineStart === undefined) {
1411
+ lineStart = directive.line;
1412
+ currentLine = directive.line;
1413
+ }
1414
+ else if (currentLine !== undefined) {
1415
+ accumulatedLineDiff += directive.line - currentLine;
1416
+ currentLine = directive.line;
1417
+ }
1418
+ }
1419
+ }
1420
+ // Emit accumulated changes before any other debug events
1421
+ const hasOtherDebugEvents = annotated.debugDirectives.some(d => d.type !== 'line');
1422
+ if (hasOtherDebugEvents && (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0 || !hasEmittedFirstDebugEvent)) {
1423
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
1424
+ accumulatedLineDiff = 0;
1425
+ accumulatedAddressDiff = 0;
1426
+ hasEmittedFirstDebugEvent = true;
1427
+ }
1428
+ // Emit other debug directives
1429
+ for (const directive of annotated.debugDirectives) {
1430
+ if (directive.type === 'startLocal') {
1431
+ debugBytecode.push({
1432
+ type: 'startLocal',
1433
+ registerNum: getRegisterNum(directive.local.register),
1434
+ name: directive.local.name,
1435
+ type_: directive.local.type,
1436
+ });
1437
+ }
1438
+ else if (directive.type === 'endLocal') {
1439
+ debugBytecode.push({
1440
+ type: 'endLocal',
1441
+ registerNum: getRegisterNum(directive.register),
1442
+ });
1443
+ }
1444
+ else if (directive.type === 'restartLocal') {
1445
+ debugBytecode.push({
1446
+ type: 'restartLocal',
1447
+ registerNum: getRegisterNum(directive.register),
1448
+ });
1449
+ }
1450
+ else if (directive.type === 'setPrologueEnd') {
1451
+ debugBytecode.push({
1452
+ type: 'setPrologueEnd',
1453
+ });
1454
+ }
1455
+ else if (directive.type === 'setEpilogueBegin') {
1456
+ debugBytecode.push({
1457
+ type: 'setEpilogueBegin',
1458
+ });
1459
+ }
1460
+ }
1461
+ }
1462
+ // Emit any remaining accumulated changes at the end
1463
+ if (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0) {
1464
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
1465
+ }
1466
+ if (lineStart !== undefined) {
1467
+ // Extract parameter names from .param directives
1468
+ const parameterNames = [];
1469
+ // TODO: Extract from parameters array if needed
1470
+ // If we have a lineStart but no bytecode, we need to emit a special opcode
1471
+ // to establish the initial state. The special value 0x0E (14) means:
1472
+ // adjusted = 14 - 10 = 4, line_diff = 4 % 15 - 4 = 0, pc_diff = 4 / 15 = 0
1473
+ if (debugBytecode.length === 0) {
1474
+ debugBytecode.push({
1475
+ type: 'special',
1476
+ value: 0x0A + 4, // 14 in decimal
1477
+ });
1478
+ }
1479
+ debugInfo = {
1480
+ lineStart,
1481
+ parameterNames,
1482
+ bytecode: debugBytecode,
1483
+ };
1484
+ }
511
1485
  return {
512
- registersSize,
513
- insSize: -1, // TODO
514
- outsSize: -1, // TODO
515
- debugInfo: undefined, // TODO
516
- instructions,
517
- tries: [], // TODO
518
- // _annotations,
1486
+ dalvikExecutableCode: {
1487
+ registersSize,
1488
+ insSize: -1, // TODO
1489
+ outsSize: -1, // TODO
1490
+ debugInfo,
1491
+ instructions: instructions, // TODO
1492
+ tries,
1493
+ // _annotations,
1494
+ },
1495
+ parameters,
1496
+ parameterAnnotations: parameters.filter(parameter => parameter.annotation !== undefined),
1497
+ methodAnnotations: annotations,
519
1498
  };
520
1499
  });
521
1500
  setParserName(smaliExecutableCodeParser, 'smaliExecutableCodeParser');
522
- const smaliMethodParser = promiseCompose(createTupleParser([
1501
+ // TODO: ???
1502
+ const sortRegistersOperations = new Set([
1503
+ 'if-eq',
1504
+ 'if-ne',
1505
+ ]);
1506
+ // All 12x format operations need register reversal because:
1507
+ // - Bytecode format is B|A (B in high nibble, A in low nibble)
1508
+ // - nibblesParser returns [B, A]
1509
+ // - Smali syntax is "op vA, vB" (destination first)
1510
+ // - So we need to reverse to get [A, B]
1511
+ const reverseRegistersOperations = new Set([
1512
+ // Move operations
1513
+ 'move',
1514
+ 'move-wide',
1515
+ 'move-object',
1516
+ // Unary operations (negation, bitwise not)
1517
+ 'neg-int',
1518
+ 'not-int',
1519
+ 'neg-long',
1520
+ 'not-long',
1521
+ 'neg-float',
1522
+ 'neg-double',
1523
+ // Type conversion operations
1524
+ 'int-to-long',
1525
+ 'int-to-float',
1526
+ 'int-to-double',
1527
+ 'long-to-int',
1528
+ 'long-to-float',
1529
+ 'long-to-double',
1530
+ 'float-to-int',
1531
+ 'float-to-long',
1532
+ 'float-to-double',
1533
+ 'double-to-int',
1534
+ 'double-to-long',
1535
+ 'double-to-float',
1536
+ 'int-to-byte',
1537
+ 'int-to-char',
1538
+ 'int-to-short',
1539
+ // Binary operations with /2addr suffix
1540
+ 'add-int/2addr',
1541
+ 'sub-int/2addr',
1542
+ 'mul-int/2addr',
1543
+ 'div-int/2addr',
1544
+ 'rem-int/2addr',
1545
+ 'and-int/2addr',
1546
+ 'or-int/2addr',
1547
+ 'xor-int/2addr',
1548
+ 'shl-int/2addr',
1549
+ 'shr-int/2addr',
1550
+ 'ushr-int/2addr',
1551
+ 'add-long/2addr',
1552
+ 'sub-long/2addr',
1553
+ 'mul-long/2addr',
1554
+ 'div-long/2addr',
1555
+ 'rem-long/2addr',
1556
+ 'and-long/2addr',
1557
+ 'or-long/2addr',
1558
+ 'xor-long/2addr',
1559
+ 'shl-long/2addr',
1560
+ 'shr-long/2addr',
1561
+ 'ushr-long/2addr',
1562
+ 'add-float/2addr',
1563
+ 'sub-float/2addr',
1564
+ 'mul-float/2addr',
1565
+ 'div-float/2addr',
1566
+ 'rem-float/2addr',
1567
+ 'add-double/2addr',
1568
+ 'sub-double/2addr',
1569
+ 'mul-double/2addr',
1570
+ 'div-double/2addr',
1571
+ 'rem-double/2addr',
1572
+ ]);
1573
+ function normalizeOperation(operation) {
1574
+ if (sortRegistersOperations.has(operation.operation)
1575
+ && 'registers' in operation) {
1576
+ operation.registers.sort((a, b) => a - b);
1577
+ }
1578
+ if (reverseRegistersOperations.has(operation.operation)
1579
+ && 'registers' in operation) {
1580
+ operation.registers.reverse();
1581
+ }
1582
+ return operation;
1583
+ }
1584
+ export const smaliMethodParser = promiseCompose(createTupleParser([
523
1585
  createExactSequenceParser('.method '),
524
- smaliAccessFlagsParser,
525
- createExactSequenceParser(' '),
1586
+ createOptionalParser(promiseCompose(createTupleParser([
1587
+ smaliAccessFlagsParser,
1588
+ smaliSingleWhitespaceParser,
1589
+ ]), ([accessFlags]) => accessFlags)),
526
1590
  smaliMemberNameParser,
527
1591
  smaliMethodPrototypeParser,
528
- createExactSequenceParser('\n'),
1592
+ smaliLineEndPraser,
529
1593
  smaliExecutableCodeParser,
530
1594
  createArrayParser(smaliCodeLineParser),
531
1595
  createExactSequenceParser('.end method\n'),
532
- ]), ([_method, accessFlags, _space, name, prototype, _newline, code, _lines, _endMethod,]) => {
533
- let insSize = 1;
534
- for (const _parameter of prototype.parameters) {
535
- insSize += 1; // TODO: two words for wide types
536
- }
537
- code.insSize = insSize;
538
- for (const operation of code.instructions) {
539
- const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation); // TODO
540
- if (!smaliRegisters.length) {
541
- continue;
1596
+ ]), ([_method, accessFlagsOrUndefined, name, prototype, _newline, { dalvikExecutableCode, parameters, parameterAnnotations, methodAnnotations, }, _lines, _endMethod,]) => {
1597
+ const accessFlags = accessFlagsOrUndefined ?? dalvikExecutableAccessFlagsDefault();
1598
+ let code = dalvikExecutableCode;
1599
+ if (accessFlags.native && code.instructions.length === 0) {
1600
+ code = undefined;
1601
+ }
1602
+ if (code) {
1603
+ const insSize = (accessFlags.static ? 0 : 1) + shortyGetInsSize(prototype.shorty);
1604
+ code.insSize = insSize;
1605
+ for (const operation of code.instructions) {
1606
+ const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation); // TODO
1607
+ if (smaliRegisters.length === 0) {
1608
+ continue;
1609
+ }
1610
+ const registers = smaliRegisters.map(({ prefix, index }) => {
1611
+ if (prefix === 'v') {
1612
+ return index;
1613
+ }
1614
+ if (prefix === 'p') {
1615
+ return code.registersSize - insSize + index;
1616
+ }
1617
+ invariant(false, 'Expected prefix to be v or p');
1618
+ });
1619
+ operation.registers = registers; // TODO
542
1620
  }
543
- const registers = smaliRegisters.map(({ prefix, index }) => {
544
- if (prefix === 'v') {
545
- return index;
1621
+ let outsSize = 0;
1622
+ for (const operation of code.instructions) {
1623
+ if (!operation.operation.startsWith('invoke-')) {
1624
+ continue;
546
1625
  }
547
- if (prefix === 'p') {
548
- return code.registersSize - insSize + index;
1626
+ const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
1627
+ outsSize = Math.max(outsSize, registers.length); // TODO?: two words for wide types?
1628
+ }
1629
+ code.outsSize = outsSize;
1630
+ // Populate debug info parameter names
1631
+ // The parameter count is insSize minus 1 for non-static methods (to exclude 'this')
1632
+ if (code.debugInfo) {
1633
+ const paramCount = accessFlags.static ? insSize : insSize - 1;
1634
+ const paramNames = Array(paramCount).fill(undefined);
1635
+ // Map parameter names from .param directives
1636
+ // For non-static methods: p0 is 'this', p1 is first param (index 0)
1637
+ // For static methods: p0 is first param (index 0)
1638
+ for (const param of parameters) {
1639
+ if (param.register.prefix === 'p' && param.name) {
1640
+ const pRegister = param.register.index;
1641
+ // Adjust for 'this' parameter in non-static methods
1642
+ const paramIndex = accessFlags.static ? pRegister : pRegister - 1;
1643
+ if (paramIndex >= 0 && paramIndex < paramCount) {
1644
+ paramNames[paramIndex] = param.name;
1645
+ }
1646
+ }
549
1647
  }
550
- invariant(false, 'Expected prefix to be v or p');
551
- });
552
- operation.registers = registers; // TODO
553
- }
554
- let outsSize = 0;
555
- for (const operation of code.instructions) {
556
- const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
557
- outsSize = Math.max(outsSize, registers.length); // TODO: two words for wide types
1648
+ code.debugInfo.parameterNames = paramNames;
1649
+ }
558
1650
  }
559
- code.outsSize = outsSize;
560
1651
  return {
561
- accessFlags,
562
- method: {
563
- class: 'FILLED_LATER',
564
- prototype,
565
- name,
1652
+ dalvikExecutableMethodWithAccess: {
1653
+ accessFlags,
1654
+ method: {
1655
+ class: 'FILLED_LATER',
1656
+ prototype,
1657
+ name,
1658
+ },
1659
+ code: code?.instructions.length
1660
+ ? {
1661
+ ...code,
1662
+ instructions: code.instructions.map(normalizeOperation),
1663
+ }
1664
+ : undefined,
566
1665
  },
567
- code,
1666
+ parameterAnnotations,
1667
+ methodAnnotations,
568
1668
  };
569
1669
  });
570
1670
  setParserName(smaliMethodParser, 'smaliMethodParser');
571
1671
  const smaliMethodsParser = promiseCompose(createArrayParser(createDisjunctionParser([
572
1672
  smaliMethodParser,
573
1673
  smaliCommentsOrNewlinesParser,
574
- ])), (methodsAndComments) => {
1674
+ ])), methodsAndComments => {
575
1675
  let type = 'directMethod';
576
1676
  const directMethods = [];
577
1677
  const virtualMethods = [];
1678
+ const parameterAnnotations = [];
1679
+ const methodAnnotations = [];
1680
+ function pushParameterAnnotation(annotation) {
1681
+ if (annotation.annotations.length === 0) {
1682
+ return;
1683
+ }
1684
+ const existingMethod = parameterAnnotations.find(parameterAnnotation => dalvikExecutableMethodEquals(parameterAnnotation.method, annotation.method));
1685
+ if (existingMethod) {
1686
+ for (const [index, parameterAnnotations_] of annotation.annotations.entries()) {
1687
+ const existingParameterAnnotations = existingMethod.annotations.at(index);
1688
+ if (existingParameterAnnotations) {
1689
+ existingParameterAnnotations.push(...parameterAnnotations_);
1690
+ }
1691
+ else {
1692
+ existingMethod.annotations[index] = parameterAnnotations_;
1693
+ }
1694
+ }
1695
+ return;
1696
+ }
1697
+ parameterAnnotations.push(annotation);
1698
+ }
578
1699
  for (const methodOrComment of methodsAndComments) {
579
1700
  if (Array.isArray(methodOrComment)) {
580
1701
  for (const comment of methodOrComment) {
@@ -589,68 +1710,372 @@ const smaliMethodsParser = promiseCompose(createArrayParser(createDisjunctionPar
589
1710
  }
590
1711
  invariant(typeof methodOrComment === 'object', 'Expected method or comment');
591
1712
  const method = methodOrComment;
1713
+ if (method.methodAnnotations.length > 0) {
1714
+ methodAnnotations.push({
1715
+ method: method.dalvikExecutableMethodWithAccess.method,
1716
+ annotations: method.methodAnnotations.map(annotation => ({
1717
+ type: annotation.type,
1718
+ visibility: annotation.visibility,
1719
+ elements: annotation.elements.map(element => ({
1720
+ name: element.name,
1721
+ value: convertToTaggedEncodedValue(element.value),
1722
+ })),
1723
+ })),
1724
+ });
1725
+ }
1726
+ // Create an annotations array for all parameters, not just those with annotations
1727
+ // In smali, instance methods have p0 as 'this', p1 as first param, etc.
1728
+ // But DEX parameter annotations only include the actual parameters (not 'this')
1729
+ const isStatic = method.dalvikExecutableMethodWithAccess.accessFlags.static;
1730
+ const smaliRegisterOffset = isStatic ? 0 : 1; // P0 is 'this' for instance methods
1731
+ const allParameterAnnotations = method.dalvikExecutableMethodWithAccess.method.prototype.parameters.map((_, parameterIndex) => {
1732
+ const smaliRegisterIndex = parameterIndex + smaliRegisterOffset;
1733
+ const parameterAnnotation = method.parameterAnnotations.find(pa => pa.register.prefix === 'p' && pa.register.index === smaliRegisterIndex);
1734
+ if (parameterAnnotation?.annotation) {
1735
+ return [{
1736
+ type: parameterAnnotation.annotation.type,
1737
+ visibility: parameterAnnotation.annotation.visibility,
1738
+ elements: parameterAnnotation.annotation.elements.map(element => ({
1739
+ name: element.name,
1740
+ value: convertToTaggedEncodedValue(element.value),
1741
+ })),
1742
+ }];
1743
+ }
1744
+ return [];
1745
+ });
1746
+ // Only push parameter annotations if there are some actual annotations
1747
+ if (allParameterAnnotations.some(annotations => annotations.length > 0)) {
1748
+ pushParameterAnnotation({
1749
+ method: method.dalvikExecutableMethodWithAccess.method,
1750
+ annotations: allParameterAnnotations,
1751
+ });
1752
+ }
592
1753
  if (type === 'directMethod') {
593
- directMethods.push(method);
1754
+ directMethods.push(method.dalvikExecutableMethodWithAccess);
594
1755
  continue;
595
1756
  }
596
1757
  if (type === 'virtualMethod') {
597
- virtualMethods.push(method);
1758
+ virtualMethods.push(method.dalvikExecutableMethodWithAccess);
598
1759
  continue;
599
1760
  }
600
1761
  invariant(false, 'Expected method type');
601
1762
  }
1763
+ // Sort parameter annotations by method index in the combined method list
1764
+ // to match the order in the DEX file's annotations directory
1765
+ const allMethods = [...directMethods, ...virtualMethods];
1766
+ parameterAnnotations.sort((a, b) => {
1767
+ const indexA = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, a.method));
1768
+ const indexB = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, b.method));
1769
+ return indexA - indexB;
1770
+ });
602
1771
  return {
603
1772
  directMethods,
604
1773
  virtualMethods,
1774
+ parameterAnnotations,
1775
+ methodAnnotations,
605
1776
  };
606
1777
  });
607
1778
  setParserName(smaliMethodsParser, 'smaliMethodsParser');
608
1779
  export const smaliParser = promiseCompose(createTupleParser([
609
1780
  smaliClassDeclarationParser,
610
1781
  smaliSuperDeclarationParser,
611
- smaliSourceDeclarationParser,
612
- smaliCommentsOrNewlinesParser,
613
- createArrayParser(smaliInterfaceDeclarationParser),
614
- smaliCommentsOrNewlinesParser,
615
- createOptionalParser(smaliFieldsParser),
1782
+ createOptionalParser(smaliSourceDeclarationParser),
1783
+ createOptionalParser(promiseCompose(createTupleParser([
1784
+ smaliCommentsOrNewlinesParser,
1785
+ createNonEmptyArrayParser(smaliInterfaceDeclarationParser),
1786
+ ]), ([_commentsOrNewlines, interfaces,]) => interfaces)),
1787
+ createOptionalParser(promiseCompose(createTupleParser([
1788
+ smaliCommentsOrNewlinesParser,
1789
+ createSeparatedNonEmptyArrayParser(smaliAnnotationParser, smaliCommentsOrNewlinesParser),
1790
+ ]), ([_commentsOrNewlines, classAnnotations,]) => classAnnotations)),
1791
+ createOptionalParser(promiseCompose(createTupleParser([
1792
+ smaliCommentsOrNewlinesParser,
1793
+ smaliFieldsParser,
1794
+ ]), ([_commentsOrNewlines, fields,]) => fields)),
616
1795
  smaliMethodsParser,
617
- ]), ([{ accessFlags, class: class_, }, { superclass, }, { sourceFile, }, _commentsOrNewlines0, interfaces, _commentsOrNewlines1, fields, methods,]) => ({
618
- accessFlags,
619
- class: class_,
620
- superclass,
621
- sourceFile,
622
- annotations: undefined, // TODO
623
- classData: {
624
- directMethods: methods.directMethods.map((method) => ({
625
- ...method,
626
- method: {
627
- ...method.method,
628
- class: class_,
629
- },
630
- })),
631
- virtualMethods: methods.virtualMethods.map((method) => ({
632
- ...method,
633
- method: {
634
- ...method.method,
635
- class: class_,
636
- },
637
- })),
638
- staticFields: (fields?.staticFields ?? []).map((field) => ({
639
- ...field,
640
- field: {
641
- ...field.field,
642
- class: class_,
643
- },
644
- })),
645
- instanceFields: (fields?.instanceFields ?? []).map((field) => ({
646
- ...field,
647
- field: {
648
- ...field.field,
649
- class: class_,
650
- },
1796
+ ]), ([{ accessFlags, class: class_, }, { superclass, }, sourceFileObject, interfaces, classAnnotations, smaliFields, methods,]) => {
1797
+ const sourceFile = sourceFileObject?.sourceFile;
1798
+ // Create staticValues array matching DEX format:
1799
+ // - Find the last static field with a non-default initializer
1800
+ // - Create array up to that index with values/nulls
1801
+ // - Fields after the last initializer are not included
1802
+ // - Default values (false, null) are not considered initializers
1803
+ const staticFieldsList = smaliFields?.staticFields ?? [];
1804
+ let lastIndexWithInitializer = -1;
1805
+ for (let i = staticFieldsList.length - 1; i >= 0; i--) {
1806
+ const initValue = staticFieldsList[i].initialValue;
1807
+ // Only consider non-default values as initializers
1808
+ // Numbers, bigints, strings, and true are non-default
1809
+ // false and null are default values
1810
+ if (initValue !== undefined && typeof initValue === 'number') {
1811
+ lastIndexWithInitializer = i;
1812
+ break;
1813
+ }
1814
+ if (initValue !== undefined && typeof initValue === 'bigint') {
1815
+ lastIndexWithInitializer = i;
1816
+ break;
1817
+ }
1818
+ if (initValue !== undefined && typeof initValue === 'string') {
1819
+ lastIndexWithInitializer = i;
1820
+ break;
1821
+ }
1822
+ if (initValue === true) {
1823
+ lastIndexWithInitializer = i;
1824
+ break;
1825
+ }
1826
+ }
1827
+ const staticValues = lastIndexWithInitializer === -1
1828
+ ? []
1829
+ : staticFieldsList
1830
+ .slice(0, lastIndexWithInitializer + 1)
1831
+ .map(smaliField => {
1832
+ const fieldType = smaliField.field.field.type;
1833
+ if (smaliField.initialValue === undefined) {
1834
+ // For integer types without initializer, DEX stores 0
1835
+ if (fieldType === 'I') {
1836
+ return { type: 'int', value: 0 };
1837
+ }
1838
+ if (fieldType === 'B') {
1839
+ return { type: 'byte', value: 0 };
1840
+ }
1841
+ if (fieldType === 'S') {
1842
+ return { type: 'short', value: 0 };
1843
+ }
1844
+ if (fieldType === 'C') {
1845
+ return { type: 'char', value: 0 };
1846
+ }
1847
+ // For long types without initializer, DEX stores 0n
1848
+ if (fieldType === 'J') {
1849
+ return { type: 'long', value: 0n };
1850
+ }
1851
+ // For float/double types without initializer, DEX stores 0
1852
+ if (fieldType === 'F') {
1853
+ return { type: 'float', value: 0 };
1854
+ }
1855
+ if (fieldType === 'D') {
1856
+ return { type: 'double', value: 0 };
1857
+ }
1858
+ // For boolean types without initializer, DEX stores false
1859
+ if (fieldType === 'Z') {
1860
+ return { type: 'boolean', value: false };
1861
+ }
1862
+ // For other types (reference types, etc.), return null
1863
+ return { type: 'null', value: null };
1864
+ }
1865
+ // Numeric values are stored in static values array
1866
+ if (typeof smaliField.initialValue === 'number') {
1867
+ // Convert to BigInt for long (J) types
1868
+ if (fieldType === 'J') {
1869
+ return { type: 'long', value: BigInt(smaliField.initialValue) };
1870
+ }
1871
+ if (fieldType === 'B') {
1872
+ return { type: 'byte', value: smaliField.initialValue };
1873
+ }
1874
+ if (fieldType === 'S') {
1875
+ return { type: 'short', value: smaliField.initialValue };
1876
+ }
1877
+ if (fieldType === 'C') {
1878
+ return { type: 'char', value: smaliField.initialValue };
1879
+ }
1880
+ if (fieldType === 'F') {
1881
+ return { type: 'float', value: smaliField.initialValue };
1882
+ }
1883
+ if (fieldType === 'D') {
1884
+ return { type: 'double', value: smaliField.initialValue };
1885
+ }
1886
+ // Default to int for other numeric types
1887
+ return { type: 'int', value: smaliField.initialValue };
1888
+ }
1889
+ // BigInt values for long (J) types
1890
+ if (typeof smaliField.initialValue === 'bigint') {
1891
+ return { type: 'long', value: smaliField.initialValue };
1892
+ }
1893
+ // String values should be stored as string type
1894
+ if (typeof smaliField.initialValue === 'string') {
1895
+ return { type: 'string', value: smaliField.initialValue };
1896
+ }
1897
+ // Boolean true is a non-default value and should be in staticValues
1898
+ if (smaliField.initialValue === true) {
1899
+ return { type: 'boolean', value: true };
1900
+ }
1901
+ // Boolean false and null are default values
1902
+ // For boolean fields with explicit false, return false
1903
+ if (smaliField.initialValue === false && fieldType === 'Z') {
1904
+ return { type: 'boolean', value: false };
1905
+ }
1906
+ // For null or other default values, return null
1907
+ return { type: 'null', value: null };
1908
+ });
1909
+ const fields = {
1910
+ staticFields: smaliFields?.staticFields.map(({ field }) => field) ?? [],
1911
+ instanceFields: smaliFields?.instanceFields.map(({ field }) => field) ?? [],
1912
+ };
1913
+ // Sort fields to match DEX file order (by field name, then type)
1914
+ // This matches the field order in class_data_item in DEX files
1915
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
1916
+ const sortFields = (fieldsList) => {
1917
+ fieldsList.sort((a, b) => {
1918
+ // First by field name (case-sensitive comparison)
1919
+ if (a.field.name !== b.field.name) {
1920
+ return a.field.name < b.field.name ? -1 : 1;
1921
+ }
1922
+ // Then by field type (case-sensitive comparison)
1923
+ return a.field.type < b.field.type ? -1 : 1;
1924
+ });
1925
+ };
1926
+ sortFields(fields.staticFields);
1927
+ sortFields(fields.instanceFields);
1928
+ const annotations = {
1929
+ classAnnotations: (classAnnotations ?? []).map(annotation => ({
1930
+ type: annotation.type,
1931
+ visibility: annotation.visibility,
1932
+ elements: annotation.elements.map(element => ({
1933
+ name: element.name,
1934
+ value: convertToTaggedEncodedValue(element.value),
1935
+ })),
651
1936
  })),
652
- },
653
- interfaces,
654
- staticValues: [], // TODO
655
- }));
1937
+ fieldAnnotations: [],
1938
+ methodAnnotations: methods.methodAnnotations,
1939
+ parameterAnnotations: methods.parameterAnnotations,
1940
+ };
1941
+ for (const smaliField of [...(smaliFields?.staticFields ?? []), ...(smaliFields?.instanceFields ?? [])]) {
1942
+ if (smaliField.annotations.length === 0) {
1943
+ continue;
1944
+ }
1945
+ const existingFieldAnnotations = annotations.fieldAnnotations.find(fieldAnnotation => dalvikExecutableFieldEquals(fieldAnnotation.field, smaliField.field.field));
1946
+ if (existingFieldAnnotations) {
1947
+ existingFieldAnnotations.annotations ??= [];
1948
+ existingFieldAnnotations.annotations.push(...smaliField.annotations.map(annotation => ({
1949
+ type: annotation.type,
1950
+ visibility: annotation.visibility,
1951
+ elements: annotation.elements.map(element => ({
1952
+ name: element.name,
1953
+ value: convertToTaggedEncodedValue(element.value),
1954
+ })),
1955
+ })));
1956
+ continue;
1957
+ }
1958
+ annotations.fieldAnnotations.push({
1959
+ field: smaliField.field.field,
1960
+ annotations: smaliField.annotations.map(annotation => ({
1961
+ type: annotation.type,
1962
+ visibility: annotation.visibility,
1963
+ elements: annotation.elements.map(element => ({
1964
+ name: element.name,
1965
+ value: convertToTaggedEncodedValue(element.value),
1966
+ })),
1967
+ })),
1968
+ });
1969
+ }
1970
+ // Sort field annotations to match DEX file order (by field name, then type)
1971
+ // This matches the annotations_directory_item field_annotations order in DEX files
1972
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
1973
+ annotations.fieldAnnotations.sort((a, b) => {
1974
+ // First by field name (case-sensitive comparison)
1975
+ if (a.field.name !== b.field.name) {
1976
+ return a.field.name < b.field.name ? -1 : 1;
1977
+ }
1978
+ // Then by field type (case-sensitive comparison)
1979
+ return a.field.type < b.field.type ? -1 : 1;
1980
+ });
1981
+ // Compute synthetic flag from members if not explicitly set
1982
+ // This matches baksmali behavior where synthetic flag at class level is not output
1983
+ // but can be inferred from all members being synthetic
1984
+ const allMembers = [
1985
+ ...fields.staticFields,
1986
+ ...fields.instanceFields,
1987
+ ...methods.directMethods,
1988
+ // Note: virtualMethods are not included, matching DEX parser behavior
1989
+ ];
1990
+ const allMembersAreSynthetic = (allMembers.every(member => member.accessFlags.synthetic)
1991
+ && allMembers.length > 0);
1992
+ const finalAccessFlags = {
1993
+ ...accessFlags,
1994
+ // Use the synthetic flag from the class declaration, or compute it from members if not set
1995
+ synthetic: accessFlags.synthetic || allMembersAreSynthetic,
1996
+ };
1997
+ return {
1998
+ accessFlags: finalAccessFlags,
1999
+ class: class_,
2000
+ superclass,
2001
+ sourceFile,
2002
+ annotations: ((annotations.classAnnotations?.length
2003
+ || annotations.fieldAnnotations?.length
2004
+ || annotations.methodAnnotations?.length
2005
+ || annotations.parameterAnnotations?.length)
2006
+ ? ({
2007
+ classAnnotations: annotations.classAnnotations,
2008
+ fieldAnnotations: annotations.fieldAnnotations.map(fieldAnnotation => ({
2009
+ ...fieldAnnotation,
2010
+ field: {
2011
+ ...fieldAnnotation.field,
2012
+ class: class_,
2013
+ },
2014
+ })),
2015
+ methodAnnotations: annotations.methodAnnotations
2016
+ .map(methodAnnotation => ({
2017
+ ...methodAnnotation,
2018
+ method: {
2019
+ ...methodAnnotation.method,
2020
+ class: class_,
2021
+ },
2022
+ }))
2023
+ // Sort method annotations to match DEX file order (lexicographic by method name, then prototype shorty)
2024
+ // This matches the method_idx order in the DEX file's method_id table
2025
+ .sort((a, b) => {
2026
+ // Sort by method name first (using code point comparison, not locale-aware)
2027
+ if (a.method.name !== b.method.name) {
2028
+ return a.method.name < b.method.name ? -1 : 1;
2029
+ }
2030
+ // Then by shorty (prototype signature)
2031
+ return a.method.prototype.shorty < b.method.prototype.shorty ? -1 : 1;
2032
+ }),
2033
+ parameterAnnotations: annotations.parameterAnnotations.map(parameterAnnotation => ({
2034
+ ...parameterAnnotation,
2035
+ method: {
2036
+ ...parameterAnnotation.method,
2037
+ class: class_,
2038
+ },
2039
+ })),
2040
+ })
2041
+ : undefined),
2042
+ classData: ((methods.directMethods.length > 0
2043
+ || methods.virtualMethods.length > 0
2044
+ || (fields?.staticFields.length ?? 0)
2045
+ || (fields?.instanceFields.length ?? 0))
2046
+ ? ({
2047
+ directMethods: methods.directMethods.map(method => ({
2048
+ ...method,
2049
+ method: {
2050
+ ...method.method,
2051
+ class: class_,
2052
+ },
2053
+ })),
2054
+ virtualMethods: methods.virtualMethods.map(method => ({
2055
+ ...method,
2056
+ method: {
2057
+ ...method.method,
2058
+ class: class_,
2059
+ },
2060
+ })),
2061
+ staticFields: (fields?.staticFields ?? []).map(field => ({
2062
+ ...field,
2063
+ field: {
2064
+ ...field.field,
2065
+ class: class_,
2066
+ },
2067
+ })),
2068
+ instanceFields: (fields?.instanceFields ?? []).map(field => ({
2069
+ ...field,
2070
+ field: {
2071
+ ...field.field,
2072
+ class: class_,
2073
+ },
2074
+ })),
2075
+ })
2076
+ : undefined),
2077
+ interfaces: interfaces ?? [],
2078
+ staticValues,
2079
+ };
2080
+ });
656
2081
  setParserName(smaliParser, 'smaliParser');