@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,32 +1,191 @@
1
- import invariant from "invariant";
2
- import { DalvikBytecode, DalvikBytecodeOperation, dalvikBytecodeOperationCompanion } from "./dalvikBytecodeParser.js";
3
- import { DalvikExecutableAccessFlags, DalvikExecutableClassData, DalvikExecutableClassDefinition, DalvikExecutableCode, DalvikExecutableField, DalvikExecutableFieldWithAccess, DalvikExecutableMethod, DalvikExecutableMethodWithAccess, DalvikExecutablePrototype, isDalvikExecutableField, isDalvikExecutableMethod } from "./dalvikExecutable.js";
4
- import { createExactSequenceParser } from "./exactSequenceParser.js";
5
- import { cloneParser, Parser, setParserName } from "./parser.js";
6
- import { ParserContext } from "./parserContext.js";
7
- import { promiseCompose } from "./promiseCompose.js";
8
- import { createTupleParser } from "./tupleParser.js";
9
- import { createUnionParser } from "./unionParser.js";
10
- import { createArrayParser } from "./arrayParser.js";
11
- import { jsonNumberParser, jsonStringParser } from "./jsonParser.js";
12
- import { createNonEmptyArrayParser } from "./nonEmptyArrayParser.js";
13
- import { createOptionalParser } from "./optionalParser.js";
14
- import { createNegativeLookaheadParser } from "./negativeLookaheadParser.js";
15
- import { createSeparatedArrayParser } from "./separatedArrayParser.js";
16
- import { smaliMemberNameParser, smaliTypeDescriptorParser } from "./dalvikExecutableParser/stringSyntaxParser.js";
17
- import { createDisjunctionParser } from "./disjunctionParser.js";
1
+ import invariant from 'invariant';
2
+ import { type Simplify } from 'type-fest';
3
+ import { type DalvikBytecode, type DalvikBytecodeOperation, dalvikBytecodeOperationCompanion } from './dalvikBytecodeParser.js';
4
+ import {
5
+ type DalvikExecutableAccessFlags, dalvikExecutableAccessFlagsDefault, type DalvikExecutableAnnotation, type DalvikExecutableClassAnnotations, type DalvikExecutableClassData, type DalvikExecutableClassDefinition, type DalvikExecutableClassMethodAnnotation, type DalvikExecutableClassParameterAnnotation, type DalvikExecutableCode, type DalvikExecutableDebugInfo, type DalvikExecutableEncodedValue, type DalvikExecutableField, dalvikExecutableFieldEquals, type DalvikExecutableFieldWithAccess, type DalvikExecutableMethod, dalvikExecutableMethodEquals, type DalvikExecutableMethodWithAccess, type DalvikExecutablePrototype, isDalvikExecutableField, isDalvikExecutableMethod,
6
+ } from './dalvikExecutable.js';
7
+ import { createExactSequenceParser } from './exactSequenceParser.js';
8
+ import { cloneParser, type Parser, setParserName } from './parser.js';
9
+ import { type ParserContext } from './parserContext.js';
10
+ import { promiseCompose } from './promiseCompose.js';
11
+ import { createTupleParser } from './tupleParser.js';
12
+ import { createUnionParser } from './unionParser.js';
13
+ import { createArrayParser } from './arrayParser.js';
14
+ import { jsonNumberParser, jsonStringParser } from './jsonParser.js';
15
+ import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
16
+ import { createOptionalParser } from './optionalParser.js';
17
+ import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
18
+ import { createSeparatedArrayParser } from './separatedArrayParser.js';
19
+ import { smaliMemberNameParser, smaliTypeDescriptorParser } from './dalvikExecutableParser/stringSyntaxParser.js';
20
+ import { createDisjunctionParser } from './disjunctionParser.js';
21
+ import { formatSizes } from './dalvikBytecodeParser/formatSizes.js';
22
+ import { operationFormats } from './dalvikBytecodeParser/operationFormats.js';
23
+ import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
24
+ import { parserCreatorCompose } from './parserCreatorCompose.js';
25
+ import { type IndexIntoMethodIds } from './dalvikExecutableParser/typedNumbers.js';
26
+ import { createDebugLogInputParser } from './debugLogInputParser.js';
27
+ import { createDebugLogParser } from './debugLogParser.js';
28
+ import { createElementParser } from './elementParser.js';
29
+ import { createTerminatedArrayParser } from './terminatedArrayParser.js';
30
+ import { createParserAccessorParser } from './parserAccessorParser.js';
31
+
32
+ function shortyFromLongy(longy: string): string {
33
+ if (longy.startsWith('[')) {
34
+ return 'L';
35
+ }
36
+
37
+ return longy.slice(0, 1);
38
+ }
39
+
40
+ function getOperationFormatSize(operation: SmaliCodeOperation): number {
41
+ if (operation.operation === 'packed-switch-payload') {
42
+ return (operation.branchOffsetIndices.length * 2) + 4;
43
+ }
44
+
45
+ if (operation.operation === 'sparse-switch-payload') {
46
+ return (operation.branchOffsetIndices.length * 4) + 2;
47
+ }
48
+
49
+ if (operation.operation === 'fill-array-data-payload') {
50
+ const dataSize = operation.data.length; // in bytes
51
+ const paddingSize = dataSize % 2; // 1 if odd, 0 if even
52
+ const totalBytes = 8 + dataSize + paddingSize; // header (8 bytes) + data + padding
53
+ return totalBytes / 2; // Convert to code units (1 code unit = 2 bytes)
54
+ }
55
+
56
+ const operationFormat = operationFormats[operation.operation as keyof typeof operationFormats];
57
+ invariant(operationFormat, 'Unknown operation format for "%s" (operation: %o)', operation.operation, operation);
58
+
59
+ const operationSize = formatSizes[operationFormat];
60
+ invariant(operationSize, 'Unknown operation size for format %s of operation %s', operationFormat, operation.operation);
61
+
62
+ return operationSize;
63
+ }
64
+
65
+ // Helper function to convert raw annotation element values to tagged encoded values
66
+ function convertToTaggedEncodedValue(wrappedValue: SmaliAnnotationElementValue): DalvikExecutableEncodedValue {
67
+ const { kind, value } = wrappedValue;
68
+
69
+ // Handle type descriptors
70
+ if (kind === 'type') {
71
+ if (Array.isArray(value)) {
72
+ // Array of type descriptors
73
+ return { type: 'array', value: value.map(v => ({ type: 'type', value: v })) };
74
+ }
75
+ // Single type descriptor
76
+ return { type: 'type', value };
77
+ }
78
+
79
+ // Handle string literals
80
+ if (kind === 'string') {
81
+ if (Array.isArray(value)) {
82
+ // Array of strings
83
+ return { type: 'array', value: value.map(v => ({ type: 'string', value: v })) };
84
+ }
85
+ // Single string
86
+ return { type: 'string', value };
87
+ }
88
+
89
+ // Handle enum values
90
+ if (kind === 'enum') {
91
+ if (Array.isArray(value)) {
92
+ // Array of enum values
93
+ return { type: 'array', value: value.map(v => ({ type: 'enum', value: v })) };
94
+ }
95
+ // Single enum value
96
+ return { type: 'enum', value };
97
+ }
98
+
99
+ // Handle raw values (everything else)
100
+ // Handle null
101
+ if (value === null) {
102
+ return { type: 'null', value: null };
103
+ }
104
+
105
+ // Handle boolean
106
+ if (typeof value === 'boolean') {
107
+ return { type: 'boolean', value };
108
+ }
109
+
110
+ // Handle numbers - we need to determine the type based on context
111
+ // For annotation elements from smali, we'll use 'int' as the default
112
+ if (typeof value === 'number') {
113
+ // Check if it's a float or integer
114
+ if (!Number.isInteger(value)) {
115
+ return { type: 'float', value };
116
+ }
117
+ // For integers, default to 'int' type
118
+ // The actual byte/short/int distinction would need more context
119
+ return { type: 'int', value };
120
+ }
121
+
122
+ // Handle bigint
123
+ if (typeof value === 'bigint') {
124
+ return { type: 'long', value };
125
+ }
126
+
127
+ // Handle DalvikExecutableField (including enums)
128
+ if (isDalvikExecutableField(value)) {
129
+ // Note: We can't distinguish between 'field' and 'enum' without more context
130
+ // Default to 'field' type - the context in smali might help distinguish later
131
+ return { type: 'field', value };
132
+ }
133
+
134
+ // Handle DalvikExecutableMethod
135
+ if (isDalvikExecutableMethod(value)) {
136
+ return { type: 'method', value };
137
+ }
138
+
139
+ // Handle DalvikExecutablePrototype (method type)
140
+ if (typeof value === 'object' && value !== null && 'returnType' in value && 'parameters' in value && 'shorty' in value) {
141
+ return { type: 'methodType', value: value as DalvikExecutablePrototype };
142
+ }
143
+
144
+ // Handle annotations (subannotations)
145
+ if (typeof value === 'object' && value !== null && 'type' in value && 'elements' in value) {
146
+ const subannotation = value as SmaliSubannotation;
147
+ // Convert subannotation to DalvikExecutableAnnotation
148
+ const annotation: DalvikExecutableAnnotation = {
149
+ type: subannotation.type,
150
+ visibility: 'build', // Subannotations default to 'build' visibility
151
+ elements: subannotation.elements.map(element => ({
152
+ name: element.name,
153
+ value: convertToTaggedEncodedValue(element.value),
154
+ })),
155
+ };
156
+ return { type: 'annotation', value: annotation };
157
+ }
158
+
159
+ // Handle arrays
160
+ if (Array.isArray(value)) {
161
+ return { type: 'array', value: value.map(v => convertToTaggedEncodedValue({ kind: 'raw', value: v })) };
162
+ }
163
+
164
+ // Fallback - this shouldn't happen in well-formed smali
165
+ throw new Error(`Cannot convert value to tagged encoded value: ${JSON.stringify(value)}`);
166
+ }
167
+
18
168
 
19
169
  const smaliNewlinesParser: Parser<void, string> = promiseCompose(
20
170
  createNonEmptyArrayParser(createExactSequenceParser('\n')),
21
- (_newlines) => undefined,
171
+ _newlines => undefined,
22
172
  );
23
173
 
24
- const smaliOptionalIndentationParser: Parser<void, string> = promiseCompose(
25
- createArrayParser(createExactSequenceParser(' ')),
26
- (_indentation) => undefined,
174
+ const smaliSingleWhitespaceParser = createExactSequenceParser(' ');
175
+
176
+ const smaliWhitespaceParser: Parser<void, string> = promiseCompose(
177
+ createArrayParser(smaliSingleWhitespaceParser),
178
+ _indentation => undefined,
179
+ );
180
+
181
+ const smaliSingleIndentationParser = createExactSequenceParser(' ');
182
+
183
+ const smaliIndentationParser: Parser<void, string> = promiseCompose(
184
+ createArrayParser(smaliSingleIndentationParser),
185
+ _indentation => undefined,
27
186
  );
28
187
 
29
- const smaliCommentParser: Parser<string, string> = promiseCompose(
188
+ export const smaliCommentParser: Parser<string, string> = promiseCompose(
30
189
  createTupleParser([
31
190
  createExactSequenceParser('#'),
32
191
  (async (parserContext: ParserContext<string, string>) => {
@@ -47,6 +206,8 @@ const smaliCommentParser: Parser<string, string> = promiseCompose(
47
206
  continue;
48
207
  }
49
208
 
209
+ parserContext.skip(1);
210
+
50
211
  break;
51
212
  }
52
213
 
@@ -59,12 +220,38 @@ const smaliCommentParser: Parser<string, string> = promiseCompose(
59
220
  ]) => comment,
60
221
  );
61
222
 
223
+ const smaliIndentedCommentParser: Parser<string, string> = promiseCompose(
224
+ createTupleParser([
225
+ createNonEmptyArrayParser(smaliSingleIndentationParser),
226
+ smaliCommentParser,
227
+ ]),
228
+ ([
229
+ _indentation,
230
+ comment,
231
+ ]) => comment,
232
+ );
233
+
62
234
  const smaliCommentsOrNewlinesParser: Parser<string[], string> = promiseCompose(
63
235
  createArrayParser(createUnionParser<undefined | string, string, string>([
64
236
  smaliNewlinesParser,
237
+ smaliIndentedCommentParser,
65
238
  smaliCommentParser,
66
239
  ])),
67
- (newlinesOrComments) => newlinesOrComments.filter((newlineOrComment): newlineOrComment is string => typeof newlineOrComment === 'string'),
240
+ newlinesOrComments => newlinesOrComments.filter((newlineOrComment): newlineOrComment is string => typeof newlineOrComment === 'string'),
241
+ );
242
+
243
+ const smaliLineEndPraser: Parser<undefined | string, string> = promiseCompose(
244
+ createTupleParser([
245
+ createOptionalParser(smaliWhitespaceParser),
246
+ createUnionParser<undefined | string, string, string>([
247
+ smaliNewlinesParser,
248
+ smaliCommentParser,
249
+ ]),
250
+ ]),
251
+ ([
252
+ _optionalWhitespace,
253
+ newlineOrComment,
254
+ ]) => newlineOrComment,
68
255
  );
69
256
 
70
257
  const smaliIdentifierParser: Parser<string, string> = async (parserContext: ParserContext<string, string>) => {
@@ -79,15 +266,15 @@ const smaliIdentifierParser: Parser<string, string> = async (parserContext: Pars
79
266
 
80
267
  if (
81
268
  character === '_'
82
- || (
83
- character >= 'a' && character <= 'z'
84
- )
85
- || (
86
- character >= 'A' && character <= 'Z'
87
- )
88
- || (
89
- character >= '0' && character <= '9'
90
- )
269
+ || (
270
+ character >= 'a' && character <= 'z'
271
+ )
272
+ || (
273
+ character >= 'A' && character <= 'Z'
274
+ )
275
+ || (
276
+ character >= '0' && character <= '9'
277
+ )
91
278
  ) {
92
279
  parserContext.skip(1);
93
280
 
@@ -106,50 +293,232 @@ const smaliIdentifierParser: Parser<string, string> = async (parserContext: Pars
106
293
 
107
294
  setParserName(smaliIdentifierParser, 'smaliIdentifierParser');
108
295
 
109
- const smaliQuotedStringParser: Parser<string, string> = jsonStringParser;
296
+ const elementParser = createElementParser<string>();
297
+
298
+ const smaliHexNumberParser: Parser<number | bigint, string> = promiseCompose(
299
+ createTupleParser([
300
+ createOptionalParser(createExactSequenceParser('-')),
301
+ createExactSequenceParser('0x'),
302
+ createArrayParser(parserCreatorCompose(
303
+ () => elementParser,
304
+ character => async parserContext => {
305
+ parserContext.invariant(
306
+ (
307
+ (character >= '0' && character <= '9')
308
+ || (character >= 'a' && character <= 'f')
309
+ || (character >= 'A' && character <= 'F')
310
+ ),
311
+ 'Expected "0" to "9", "a" to "f", "A" to "F", got "%s"',
312
+ character,
313
+ );
314
+
315
+ return character;
316
+ },
317
+ )()),
318
+ createOptionalParser(createExactSequenceParser('L')),
319
+ ]),
320
+ ([
321
+ optionalMinus,
322
+ _0x,
323
+ valueCharacters,
324
+ optionalL,
325
+ ]) => {
326
+ const value = valueCharacters.join('');
327
+
328
+ // If the 'L' suffix is present, use BigInt for long values
329
+ if (optionalL) {
330
+ const sign = optionalMinus ? -1n : 1n;
331
+ return sign * BigInt('0x' + value);
332
+ }
333
+
334
+ const sign = optionalMinus ? -1 : 1;
335
+ return sign * Number.parseInt(value, 16);
336
+ },
337
+ );
338
+
339
+ const smaliNumberParser = createUnionParser<number, string>([
340
+ promiseCompose(
341
+ createTupleParser([
342
+ createNegativeLookaheadParser(createUnionParser([
343
+ createExactSequenceParser('0x'),
344
+ createExactSequenceParser('-0x'),
345
+ ])),
346
+ jsonNumberParser,
347
+ createOptionalParser(createUnionParser([
348
+ createExactSequenceParser('f'),
349
+ createExactSequenceParser('F'),
350
+ ])),
351
+ ]),
352
+ ([
353
+ _not0x,
354
+ number,
355
+ optionalFloatSuffix,
356
+ ]) => {
357
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
358
+ // to match what would be stored in a DEX file
359
+ if (optionalFloatSuffix) {
360
+ const float32Array = new Float32Array(1);
361
+ float32Array[0] = number;
362
+ return float32Array[0];
363
+ }
364
+
365
+ return number;
366
+ },
367
+ ),
368
+ promiseCompose(
369
+ smaliHexNumberParser,
370
+ value => {
371
+ // For smaliNumberParser, we need to ensure we return a number
372
+ // BigInt values from hex parser should be converted if they fit in number range
373
+ if (typeof value === 'bigint') {
374
+ // This shouldn't happen in contexts where smaliNumberParser is used
375
+ // (registers, line numbers, etc) but if it does, convert to number
376
+ return Number(value);
377
+ }
378
+ return value;
379
+ },
380
+ ),
381
+ ]);
382
+
383
+ setParserName(smaliNumberParser, 'smaliNumberParser');
384
+
385
+ // Parser for field initial values that can include BigInt
386
+ const smaliFieldValueParser = createUnionParser<number | bigint, string>([
387
+ promiseCompose(
388
+ createTupleParser([
389
+ createNegativeLookaheadParser(createUnionParser([
390
+ createExactSequenceParser('0x'),
391
+ createExactSequenceParser('-0x'),
392
+ ])),
393
+ jsonNumberParser,
394
+ createOptionalParser(createUnionParser([
395
+ createExactSequenceParser('f'),
396
+ createExactSequenceParser('F'),
397
+ ])),
398
+ ]),
399
+ ([
400
+ _not0x,
401
+ number,
402
+ optionalFloatSuffix,
403
+ ]) => {
404
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
405
+ // to match what would be stored in a DEX file
406
+ if (optionalFloatSuffix) {
407
+ const float32Array = new Float32Array(1);
408
+ float32Array[0] = number;
409
+ return float32Array[0];
410
+ }
411
+
412
+ return number;
413
+ },
414
+ ),
415
+ smaliHexNumberParser,
416
+ ]);
417
+
418
+ const smaliQuotedStringParser: Parser<string, string> = promiseCompose(
419
+ jsonStringParser,
420
+ string => string.replaceAll(String.raw`\'`, '\''),
421
+ );
422
+
423
+ // Parser for smali character literals (e.g., 'a', ':', '\'', '\\')
424
+ const smaliCharacterLiteralParser: Parser<number, string> = promiseCompose(
425
+ createTupleParser([
426
+ createExactSequenceParser('\''),
427
+ createDisjunctionParser<string, string>([
428
+ // Handle escape sequences (must come before regular characters)
429
+ promiseCompose(createExactSequenceParser(String.raw`\\`), () => '\\'),
430
+ promiseCompose(createExactSequenceParser(String.raw`\'`), () => '\''),
431
+ promiseCompose(createExactSequenceParser(String.raw`\"`), () => '"'),
432
+ promiseCompose(createExactSequenceParser(String.raw`\n`), () => '\n'),
433
+ promiseCompose(createExactSequenceParser(String.raw`\r`), () => '\r'),
434
+ promiseCompose(createExactSequenceParser(String.raw`\t`), () => '\t'),
435
+ promiseCompose(createExactSequenceParser(String.raw`\b`), () => '\b'),
436
+ promiseCompose(createExactSequenceParser(String.raw`\f`), () => '\f'),
437
+ // Handle regular characters (not a single quote)
438
+ parserCreatorCompose(
439
+ () => createElementParser<string>(),
440
+ character => async parserContext => {
441
+ parserContext.invariant(character !== '\'', 'Unexpected single quote');
442
+ return character;
443
+ },
444
+ )(),
445
+ ]),
446
+ createExactSequenceParser('\''),
447
+ ]),
448
+ ([, character]) => character.charCodeAt(0),
449
+ );
450
+
451
+ setParserName(smaliCharacterLiteralParser, 'smaliCharacterLiteralParser');
452
+
453
+ // Parser that matches identifier continuation characters (letters, digits, $, -, _)
454
+ const smaliIdentifierContinuationParser: Parser<string, string> = async (parserContext: ParserContext<string, string>) => {
455
+ const character = await parserContext.peek(0);
456
+
457
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
458
+
459
+ invariant(character !== undefined, 'Unexpected end of input');
460
+
461
+ parserContext.invariant(
462
+ (character >= 'a' && character <= 'z')
463
+ || (character >= 'A' && character <= 'Z')
464
+ || (character >= '0' && character <= '9')
465
+ || character === '$'
466
+ || character === '-'
467
+ || character === '_',
468
+ 'Expected identifier continuation character, got "%s"',
469
+ character,
470
+ );
471
+
472
+ parserContext.skip(1);
473
+
474
+ return character;
475
+ };
476
+
477
+ setParserName(smaliIdentifierContinuationParser, 'smaliIdentifierContinuationParser');
478
+
479
+ // Helper to create an access flag parser with word boundary check
480
+ const createAccessFlagParser = (keyword: string): Parser<typeof keyword, string> => promiseCompose(
481
+ createTupleParser([
482
+ createExactSequenceParser(keyword),
483
+ createNegativeLookaheadParser(smaliIdentifierContinuationParser),
484
+ ]),
485
+ ([flag]) => flag,
486
+ );
110
487
 
111
488
  const smaliAccessFlagsParser: Parser<DalvikExecutableAccessFlags, string> = promiseCompose(
112
489
  createSeparatedArrayParser(
113
- createUnionParser<keyof DalvikExecutableAccessFlags, string>([
114
- createExactSequenceParser('public'),
115
- createExactSequenceParser('protected'),
116
- createExactSequenceParser('private'),
117
- createExactSequenceParser('final'),
118
- createExactSequenceParser('brigde'),
119
- createExactSequenceParser('synthetic'),
120
- createExactSequenceParser('varargs'),
121
- createExactSequenceParser('static'),
122
- createExactSequenceParser('constructor'),
123
- createExactSequenceParser('abstract'),
124
- // ... TODO
490
+ createUnionParser<keyof DalvikExecutableAccessFlags | 'declared-synchronized', string>([
491
+ createAccessFlagParser('public'),
492
+ createAccessFlagParser('protected'),
493
+ createAccessFlagParser('private'),
494
+ createAccessFlagParser('final'),
495
+ createAccessFlagParser('bridge'),
496
+ createAccessFlagParser('synthetic'),
497
+ createAccessFlagParser('varargs'),
498
+ createAccessFlagParser('static'),
499
+ createAccessFlagParser('constructor'),
500
+ createAccessFlagParser('abstract'),
501
+ createAccessFlagParser('native'),
502
+ createAccessFlagParser('volatile'),
503
+ createAccessFlagParser('transient'),
504
+ createAccessFlagParser('synchronized'),
505
+ createAccessFlagParser('declared-synchronized'),
506
+ createAccessFlagParser('strict'),
507
+ createAccessFlagParser('interface'),
508
+ createAccessFlagParser('annotation'),
509
+ createAccessFlagParser('enum'),
125
510
  ]),
126
- createExactSequenceParser(' '),
511
+ smaliSingleWhitespaceParser,
127
512
  ),
128
- (accessFlagNames) => {
129
- const accessFlags = {
130
- public: false,
131
- private: false,
132
- protected: false,
133
- static: false,
134
- final: false,
135
- synchronized: false,
136
- volatile: false,
137
- bridge: false,
138
- transient: false,
139
- varargs: false,
140
- native: false,
141
- interface: false,
142
- abstract: false,
143
- strict: false,
144
- synthetic: false,
145
- annotation: false,
146
- enum: false,
147
- constructor: false,
148
- declaredSynchronized: false,
149
- };
513
+ accessFlagNames => {
514
+ const accessFlags = dalvikExecutableAccessFlagsDefault();
150
515
 
151
516
  for (const accessFlagName of accessFlagNames) {
152
- accessFlags[accessFlagName] = true;
517
+ if (accessFlagName === 'declared-synchronized') {
518
+ accessFlags.declaredSynchronized = true;
519
+ } else {
520
+ accessFlags[accessFlagName] = true;
521
+ }
153
522
  }
154
523
 
155
524
  return accessFlags;
@@ -160,18 +529,23 @@ const smaliClassDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<u
160
529
  createTupleParser([
161
530
  smaliCommentsOrNewlinesParser,
162
531
  createExactSequenceParser('.class '),
163
- smaliAccessFlagsParser,
164
- createExactSequenceParser(' '),
532
+ createOptionalParser(promiseCompose(
533
+ createTupleParser([
534
+ smaliAccessFlagsParser,
535
+ smaliSingleWhitespaceParser,
536
+ ]),
537
+ ([
538
+ accessFlags,
539
+ ]) => accessFlags,
540
+ )),
165
541
  smaliTypeDescriptorParser,
166
- createExactSequenceParser('\n'),
542
+ smaliLineEndPraser,
167
543
  ]),
168
544
  ([
169
545
  _commentsOrNewlines,
170
546
  _dotClass,
171
- accessFlags,
172
- _space,
547
+ accessFlags = dalvikExecutableAccessFlagsDefault(),
173
548
  classPath,
174
- _newline,
175
549
  ]) => ({
176
550
  accessFlags,
177
551
  class: classPath,
@@ -184,7 +558,7 @@ const smaliSuperDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<u
184
558
  createTupleParser([
185
559
  createExactSequenceParser('.super '),
186
560
  smaliTypeDescriptorParser,
187
- createExactSequenceParser('\n'),
561
+ smaliLineEndPraser,
188
562
  ]),
189
563
  ([
190
564
  _super,
@@ -201,7 +575,7 @@ const smaliInterfaceDeclarationParser: Parser<string, string> = promiseCompose(
201
575
  createTupleParser([
202
576
  createExactSequenceParser('.implements '),
203
577
  smaliTypeDescriptorParser,
204
- createExactSequenceParser('\n'),
578
+ smaliLineEndPraser,
205
579
  ]),
206
580
  ([
207
581
  _interface,
@@ -216,7 +590,7 @@ const smaliSourceDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<
216
590
  createTupleParser([
217
591
  createExactSequenceParser('.source '),
218
592
  smaliQuotedStringParser,
219
- createExactSequenceParser('\n'),
593
+ smaliLineEndPraser,
220
594
  ]),
221
595
  ([
222
596
  _source,
@@ -227,122 +601,42 @@ const smaliSourceDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<
227
601
  }),
228
602
  );
229
603
 
230
- export const smaliFieldParser: Parser<DalvikExecutableFieldWithAccess, string> = promiseCompose(
604
+ // Wrapper type to distinguish different value types in smali annotation elements
605
+ type SmaliAnnotationElementValue =
606
+ | { kind: 'type'; value: string | string[] }
607
+ | { kind: 'string'; value: string | string[] }
608
+ | { kind: 'enum'; value: DalvikExecutableField | DalvikExecutableField[] }
609
+ | { kind: 'raw'; value: unknown };
610
+
611
+ type SmaliAnnotationElement = {
612
+ name: string;
613
+ value: SmaliAnnotationElementValue;
614
+ };
615
+
616
+ const smaliEnumValueParser: Parser<DalvikExecutableField, string> = promiseCompose(
231
617
  createTupleParser([
232
- createExactSequenceParser('.field '),
233
- smaliAccessFlagsParser,
234
- createExactSequenceParser(' '),
618
+ createExactSequenceParser('.enum '),
619
+ smaliTypeDescriptorParser,
620
+ createExactSequenceParser('->'),
235
621
  smaliMemberNameParser,
236
622
  createExactSequenceParser(':'),
237
623
  smaliTypeDescriptorParser,
238
- createExactSequenceParser('\n'),
239
624
  ]),
240
625
  ([
241
- _field,
242
- accessFlags,
243
- _space,
244
- name,
626
+ _enum,
627
+ classType,
628
+ _arrow,
629
+ fieldName,
245
630
  _colon,
246
- type,
247
- ]) => {
248
- return {
249
- accessFlags,
250
- field: {
251
- class: 'FILLED_LATER',
252
- type,
253
- name,
254
- },
255
- };
256
- },
257
- );
258
-
259
- setParserName(smaliFieldParser, 'smaliFieldParser');
260
-
261
- type SmaliFields = Pick<DalvikExecutableClassData<DalvikBytecode>, 'instanceFields' | 'staticFields'>;
262
-
263
- const smaliFieldsParser: Parser<SmaliFields, string> = promiseCompose(
264
- createArrayParser<string[] | DalvikExecutableFieldWithAccess, string>(
265
- createDisjunctionParser<string[] | DalvikExecutableFieldWithAccess, string, string>([
266
- smaliFieldParser,
267
- smaliCommentsOrNewlinesParser,
268
- ]),
269
- ),
270
- (fieldsAndComments) => {
271
- let type: 'staticField' | 'instanceField' = 'instanceField';
272
-
273
- const staticFields: DalvikExecutableFieldWithAccess[] = [];
274
- const instanceFields: DalvikExecutableFieldWithAccess[] = [];
275
-
276
- for (const fieldOrComment of fieldsAndComments) {
277
- if (Array.isArray(fieldOrComment)) {
278
- for (const comment of fieldOrComment) {
279
- if (comment === ' static fields') {
280
- type = 'staticField';
281
- }
282
-
283
- if (comment === ' instance fields') {
284
- type = 'instanceField';
285
- }
286
- }
287
-
288
- continue;
289
- }
290
-
291
- invariant(typeof fieldOrComment === 'object', 'Expected field or comment');
292
-
293
- const field = fieldOrComment as DalvikExecutableFieldWithAccess;
294
-
295
- if (type === 'staticField') {
296
- staticFields.push(field);
297
-
298
- continue;
299
- }
300
-
301
- if (type === 'instanceField') {
302
- instanceFields.push(field);
303
-
304
- continue;
305
- }
306
-
307
- invariant(false, 'Expected field type');
308
- }
309
-
310
- return {
311
- staticFields,
312
- instanceFields,
313
- };
314
- }
631
+ fieldType,
632
+ ]) => ({
633
+ class: classType,
634
+ type: fieldType,
635
+ name: fieldName,
636
+ }),
315
637
  );
316
638
 
317
- setParserName(smaliFieldsParser, 'smaliFieldsParser');
318
-
319
- const smaliShortyFieldTypeParser: Parser<string, string> = createUnionParser([
320
- createExactSequenceParser('Z'),
321
- createExactSequenceParser('B'),
322
- createExactSequenceParser('S'),
323
- createExactSequenceParser('C'),
324
- createExactSequenceParser('I'),
325
- createExactSequenceParser('J'),
326
- createExactSequenceParser('F'),
327
- createExactSequenceParser('D'),
328
- ]);
329
-
330
- setParserName(smaliShortyFieldTypeParser, 'smaliShortyFieldTypeParser');
331
-
332
- const smaliShortyReturnTypeParser: Parser<string, string> = createUnionParser([
333
- createExactSequenceParser('V'),
334
- smaliShortyFieldTypeParser,
335
- ]);
336
-
337
- setParserName(smaliShortyReturnTypeParser, 'smaliShortyReturnTypeParser');
338
-
339
- function shortyFromLongy(longy: string): string {
340
- if (longy.startsWith('[')) {
341
- return 'L';
342
- }
343
-
344
- return longy.slice(0, 1);
345
- }
639
+ setParserName(smaliEnumValueParser, 'smaliEnumValueParser');
346
640
 
347
641
  const smaliMethodPrototypeParser: Parser<DalvikExecutablePrototype, string> = promiseCompose(
348
642
  createTupleParser([
@@ -359,7 +653,7 @@ const smaliMethodPrototypeParser: Parser<DalvikExecutablePrototype, string> = pr
359
653
  ]) => ({
360
654
  parameters,
361
655
  returnType,
362
- shorty: shortyFromLongy(returnType) + parameters.map((parameter) => {
656
+ shorty: shortyFromLongy(returnType) + parameters.map(parameter => {
363
657
  if (parameter === 'V') {
364
658
  return '';
365
659
  }
@@ -371,129 +665,469 @@ const smaliMethodPrototypeParser: Parser<DalvikExecutablePrototype, string> = pr
371
665
 
372
666
  setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
373
667
 
374
- const smaliCodeRegistersParser: Parser<number, string> = promiseCompose(
668
+ const smaliParametersMethodParser: Parser<DalvikExecutableMethod, string> = promiseCompose(
375
669
  createTupleParser([
376
- createExactSequenceParser(' .registers '),
377
- jsonNumberParser,
378
- createExactSequenceParser('\n'),
670
+ smaliTypeDescriptorParser,
671
+ createExactSequenceParser('->'),
672
+ smaliMemberNameParser,
673
+ smaliMethodPrototypeParser,
379
674
  ]),
380
675
  ([
381
- _registers,
382
- registers,
383
- _newline,
384
- ]) => registers,
676
+ classPath,
677
+ _separator,
678
+ methodName,
679
+ prototype,
680
+ ]) => ({
681
+ class: classPath,
682
+ prototype,
683
+ name: methodName,
684
+ }),
385
685
  );
386
686
 
387
- setParserName(smaliCodeRegistersParser, 'smaliCodeRegistersParser');
687
+ setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
688
+
689
+ type SmaliSubannotation = {
690
+ type: string;
691
+ elements: SmaliAnnotationElement[];
692
+ };
693
+
694
+ // Forward declaration to handle circular reference
695
+ let smaliSubannotationParser: Parser<SmaliSubannotation, string>;
388
696
 
389
- export const smaliAnnotationParser: Parser<unknown, string> = promiseCompose(
697
+ const smaliAnnotationElementParser: Parser<SmaliAnnotationElement, string> = promiseCompose(
390
698
  createTupleParser([
391
- smaliOptionalIndentationParser,
392
- createExactSequenceParser('.annotation '),
393
- createUnionParser([
394
- createExactSequenceParser('build'),
395
- createExactSequenceParser('runtime'),
396
- createExactSequenceParser('system'),
397
- ]),
398
- createExactSequenceParser(' '),
399
- smaliTypeDescriptorParser,
400
- createExactSequenceParser('\n'),
401
- smaliOptionalIndentationParser,
402
- createOptionalParser(
699
+ smaliIndentationParser,
700
+ smaliIdentifierParser,
701
+ createExactSequenceParser(' = '),
702
+ createDisjunctionParser([
703
+ promiseCompose(
704
+ createParserAccessorParser(() => smaliSubannotationParser),
705
+ value => ({ kind: 'raw' as const, value }),
706
+ ),
707
+ promiseCompose(
708
+ smaliEnumValueParser,
709
+ value => ({ kind: 'enum' as const, value }),
710
+ ),
711
+ promiseCompose(
712
+ smaliQuotedStringParser,
713
+ value => ({ kind: 'string' as const, value }),
714
+ ),
715
+ promiseCompose(
716
+ smaliParametersMethodParser,
717
+ value => ({ kind: 'raw' as const, value }),
718
+ ),
719
+ promiseCompose(
720
+ smaliTypeDescriptorParser,
721
+ value => ({ kind: 'type' as const, value }),
722
+ ),
723
+ promiseCompose(
724
+ smaliNumberParser,
725
+ value => ({ kind: 'raw' as const, value }),
726
+ ),
727
+ promiseCompose(
728
+ createExactSequenceParser('true'),
729
+ () => ({ kind: 'raw' as const, value: true }),
730
+ ),
731
+ promiseCompose(
732
+ createExactSequenceParser('false'),
733
+ () => ({ kind: 'raw' as const, value: false }),
734
+ ),
735
+ promiseCompose(
736
+ createExactSequenceParser('null'),
737
+ () => ({ kind: 'raw' as const, value: null }),
738
+ ),
739
+ promiseCompose(
740
+ createExactSequenceParser('{}'),
741
+ () => ({ kind: 'raw' as const, value: [] }),
742
+ ),
403
743
  promiseCompose(
404
744
  createTupleParser([
405
- createExactSequenceParser('value = '),
406
- createUnionParser([
407
- smaliTypeDescriptorParser,
745
+ createExactSequenceParser('{\n'),
746
+ createSeparatedArrayParser(
408
747
  promiseCompose(
409
748
  createTupleParser([
410
- createExactSequenceParser('{\n'),
411
- createSeparatedArrayParser(
412
- promiseCompose(
413
- createTupleParser([
414
- smaliOptionalIndentationParser,
415
- smaliTypeDescriptorParser,
416
- ]),
417
- ([
418
- _indentation,
419
- value,
420
- ]) => value,
421
- ),
422
- createExactSequenceParser(',\n'),
423
- ),
424
- createExactSequenceParser('\n'),
425
- smaliOptionalIndentationParser,
426
- createExactSequenceParser('}'),
749
+ smaliIndentationParser,
750
+ smaliEnumValueParser,
427
751
  ]),
428
752
  ([
429
- _openBrace,
753
+ _indentation,
430
754
  value,
431
- _closeBrace,
432
755
  ]) => value,
433
756
  ),
757
+ createExactSequenceParser(',\n'),
758
+ ),
759
+ smaliLineEndPraser,
760
+ smaliIndentationParser,
761
+ createExactSequenceParser('}'),
762
+ ]),
763
+ ([
764
+ _openBrace,
765
+ value,
766
+ _closeBrace,
767
+ ]) => ({ kind: 'enum' as const, value }),
768
+ ),
769
+ promiseCompose(
770
+ createTupleParser([
771
+ createExactSequenceParser('{\n'),
772
+ createSeparatedArrayParser(
434
773
  promiseCompose(
435
774
  createTupleParser([
436
- createExactSequenceParser('{\n'),
437
- createSeparatedArrayParser(
438
- promiseCompose(
439
- createTupleParser([
440
- smaliOptionalIndentationParser,
441
- smaliQuotedStringParser,
442
- ]),
443
- ([
444
- _indentation,
445
- value,
446
- ]) => value,
447
- ),
448
- createExactSequenceParser(',\n'),
449
- ),
450
- createExactSequenceParser('\n'),
451
- smaliOptionalIndentationParser,
452
- createExactSequenceParser('}'),
775
+ smaliIndentationParser,
776
+ smaliTypeDescriptorParser,
453
777
  ]),
454
778
  ([
455
- _openBrace,
779
+ _indentation,
456
780
  value,
457
- _closeBrace,
458
781
  ]) => value,
459
782
  ),
460
- ]),
461
- createExactSequenceParser('\n'),
783
+ createExactSequenceParser(',\n'),
784
+ ),
785
+ smaliLineEndPraser,
786
+ smaliIndentationParser,
787
+ createExactSequenceParser('}'),
462
788
  ]),
463
789
  ([
464
- _indentation,
790
+ _openBrace,
465
791
  value,
466
- _newline,
467
- ]) => value,
792
+ _closeBrace,
793
+ ]) => ({ kind: 'type' as const, value }),
468
794
  ),
469
- ),
470
- smaliOptionalIndentationParser,
795
+ promiseCompose(
796
+ createTupleParser([
797
+ createExactSequenceParser('{\n'),
798
+ createSeparatedArrayParser(
799
+ promiseCompose(
800
+ createTupleParser([
801
+ smaliIndentationParser,
802
+ smaliQuotedStringParser,
803
+ ]),
804
+ ([
805
+ _indentation,
806
+ value,
807
+ ]) => value,
808
+ ),
809
+ createExactSequenceParser(',\n'),
810
+ ),
811
+ smaliLineEndPraser,
812
+ smaliIndentationParser,
813
+ createExactSequenceParser('}'),
814
+ ]),
815
+ ([
816
+ _openBrace,
817
+ value,
818
+ _closeBrace,
819
+ ]) => ({ kind: 'string' as const, value }),
820
+ ),
821
+ promiseCompose(
822
+ createTupleParser([
823
+ createExactSequenceParser('{\n'),
824
+ createSeparatedArrayParser(
825
+ promiseCompose(
826
+ createTupleParser([
827
+ smaliIndentationParser,
828
+ smaliNumberParser,
829
+ ]),
830
+ ([
831
+ _indentation,
832
+ value,
833
+ ]) => value,
834
+ ),
835
+ createExactSequenceParser(',\n'),
836
+ ),
837
+ smaliLineEndPraser,
838
+ smaliIndentationParser,
839
+ createExactSequenceParser('}'),
840
+ ]),
841
+ ([
842
+ _openBrace,
843
+ value,
844
+ _closeBrace,
845
+ ]) => ({ kind: 'raw' as const, value }),
846
+ ),
847
+ ]),
848
+ smaliLineEndPraser,
849
+ ]),
850
+ ([
851
+ _indentation,
852
+ name,
853
+ _equalsSign,
854
+ value,
855
+ _newline,
856
+ ]): SmaliAnnotationElement => ({
857
+ name,
858
+ value: value as SmaliAnnotationElementValue,
859
+ }),
860
+ );
861
+
862
+ setParserName(smaliAnnotationElementParser, 'smaliAnnotationElementParser');
863
+
864
+ // Now define the subannotation parser
865
+ smaliSubannotationParser = promiseCompose(
866
+ createTupleParser([
867
+ createExactSequenceParser('.subannotation '),
868
+ smaliTypeDescriptorParser,
869
+ smaliLineEndPraser,
870
+ createArrayParser(smaliAnnotationElementParser),
871
+ smaliIndentationParser,
872
+ createExactSequenceParser('.end subannotation'),
873
+ ]),
874
+ ([
875
+ _subannotation,
876
+ type,
877
+ _newline,
878
+ elements,
879
+ _indentation,
880
+ _endSubannotation,
881
+ ]) => ({
882
+ type,
883
+ elements,
884
+ }),
885
+ );
886
+
887
+ setParserName(smaliSubannotationParser, 'smaliSubannotationParser');
888
+
889
+ type SmaliAnnotation = {
890
+ type: string;
891
+ elements: SmaliAnnotationElement[];
892
+ visibility: 'build' | 'runtime' | 'system';
893
+ };
894
+
895
+ export const smaliAnnotationParser: Parser<SmaliAnnotation, string> = promiseCompose(
896
+ createTupleParser([
897
+ smaliIndentationParser,
898
+ createExactSequenceParser('.annotation '),
899
+ createUnionParser<'build' | 'runtime' | 'system', string, string>([
900
+ createExactSequenceParser('build'),
901
+ createExactSequenceParser('runtime'),
902
+ createExactSequenceParser('system'),
903
+ ]),
904
+ smaliSingleWhitespaceParser,
905
+ smaliTypeDescriptorParser,
906
+ smaliLineEndPraser,
907
+ createArrayParser(smaliAnnotationElementParser),
908
+ smaliIndentationParser,
471
909
  createExactSequenceParser('.end annotation\n'),
472
910
  ]),
473
911
  ([
474
912
  _indentation0,
475
913
  _annotation,
476
- _accessFlags,
914
+ visibility,
477
915
  _space,
478
916
  type,
479
917
  _newline,
918
+ elements,
480
919
  _indentation1,
481
- value,
482
920
  _endAnnotation,
483
921
  ]) => ({
484
922
  type,
485
- value,
486
- // accessFlags,
923
+ elements,
924
+ visibility,
487
925
  }),
488
926
  );
489
927
 
490
928
  setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
491
929
 
930
+ type SmaliField = {
931
+ field: DalvikExecutableFieldWithAccess;
932
+ annotations: SmaliAnnotation[];
933
+ initialValue?: number | bigint | string | boolean | null;
934
+ };
935
+
936
+ export const smaliFieldParser: Parser<SmaliField, string> = promiseCompose(
937
+ createTupleParser([
938
+ createExactSequenceParser('.field '),
939
+ createOptionalParser(promiseCompose(
940
+ createTupleParser([
941
+ smaliAccessFlagsParser,
942
+ smaliSingleWhitespaceParser,
943
+ ]),
944
+ ([accessFlags, _space]) => accessFlags,
945
+ )),
946
+ smaliMemberNameParser,
947
+ createExactSequenceParser(':'),
948
+ smaliTypeDescriptorParser,
949
+ createOptionalParser(promiseCompose(
950
+ createTupleParser([
951
+ createExactSequenceParser(' = '),
952
+ createUnionParser<number | bigint | string | boolean | null, string>([
953
+ smaliCharacterLiteralParser,
954
+ smaliFieldValueParser,
955
+ smaliQuotedStringParser,
956
+ promiseCompose(
957
+ createExactSequenceParser('true'),
958
+ () => true,
959
+ ),
960
+ promiseCompose(
961
+ createExactSequenceParser('false'),
962
+ () => false,
963
+ ),
964
+ promiseCompose(
965
+ createExactSequenceParser('null'),
966
+ () => null,
967
+ ),
968
+ ]),
969
+ ]),
970
+ ([
971
+ _equals,
972
+ value,
973
+ ]) => value,
974
+ )),
975
+ smaliLineEndPraser,
976
+ createOptionalParser(promiseCompose(
977
+ createTupleParser([
978
+ createSeparatedArrayParser(
979
+ smaliAnnotationParser,
980
+ smaliNewlinesParser,
981
+ ),
982
+ createExactSequenceParser('.end field\n'),
983
+ ]),
984
+ ([
985
+ annotations,
986
+ _endField,
987
+ ]) => annotations,
988
+ )),
989
+ ]),
990
+ ([
991
+ _field,
992
+ accessFlags,
993
+ name,
994
+ _colon,
995
+ type,
996
+ initialValue,
997
+ _newline,
998
+ annotations,
999
+ ]) => ({
1000
+ field: {
1001
+ accessFlags: accessFlags ?? dalvikExecutableAccessFlagsDefault(),
1002
+ field: {
1003
+ class: 'FILLED_LATER',
1004
+ type,
1005
+ name,
1006
+ },
1007
+ },
1008
+ annotations: annotations ?? [],
1009
+ ...(initialValue !== undefined ? { initialValue } : {}),
1010
+ }),
1011
+ );
1012
+
1013
+ setParserName(smaliFieldParser, 'smaliFieldParser');
1014
+
1015
+ type SmaliFields = {
1016
+ staticFields: SmaliField[];
1017
+ instanceFields: SmaliField[];
1018
+ };
1019
+
1020
+ const smaliFieldsParser: Parser<SmaliFields, string> = promiseCompose(
1021
+ createSeparatedNonEmptyArrayParser<string[] | SmaliField, string>(
1022
+ smaliFieldParser,
1023
+ smaliCommentsOrNewlinesParser,
1024
+ ),
1025
+ fieldsAndComments => {
1026
+ let type: 'staticField' | 'instanceField' = 'instanceField';
1027
+
1028
+ const staticFields: SmaliField[] = [];
1029
+ const instanceFields: SmaliField[] = [];
1030
+
1031
+ for (const fieldOrComment of fieldsAndComments) {
1032
+ if (Array.isArray(fieldOrComment)) {
1033
+ for (const comment of fieldOrComment) {
1034
+ if (comment === ' static fields') {
1035
+ type = 'staticField';
1036
+ }
1037
+
1038
+ if (comment === ' instance fields') {
1039
+ type = 'instanceField';
1040
+ }
1041
+ }
1042
+
1043
+ continue;
1044
+ }
1045
+
1046
+ invariant(typeof fieldOrComment === 'object', 'Expected field or comment');
1047
+
1048
+ const field = fieldOrComment;
1049
+
1050
+ if (
1051
+ type === 'staticField'
1052
+ || field.field.accessFlags.static
1053
+ ) {
1054
+ staticFields.push(field);
1055
+
1056
+ continue;
1057
+ }
1058
+
1059
+ if (type === 'instanceField') {
1060
+ instanceFields.push(field);
1061
+
1062
+ continue;
1063
+ }
1064
+
1065
+ invariant(false, 'Expected field type');
1066
+ }
1067
+
1068
+ return {
1069
+ staticFields,
1070
+ instanceFields,
1071
+ };
1072
+ },
1073
+ );
1074
+
1075
+ setParserName(smaliFieldsParser, 'smaliFieldsParser');
1076
+
1077
+ const smaliShortyFieldTypeParser: Parser<string, string> = createUnionParser([
1078
+ createExactSequenceParser('Z'),
1079
+ createExactSequenceParser('B'),
1080
+ createExactSequenceParser('S'),
1081
+ createExactSequenceParser('C'),
1082
+ createExactSequenceParser('I'),
1083
+ createExactSequenceParser('J'),
1084
+ createExactSequenceParser('F'),
1085
+ createExactSequenceParser('D'),
1086
+ ]);
1087
+
1088
+ setParserName(smaliShortyFieldTypeParser, 'smaliShortyFieldTypeParser');
1089
+
1090
+ const smaliShortyReturnTypeParser: Parser<string, string> = createUnionParser([
1091
+ createExactSequenceParser('V'),
1092
+ smaliShortyFieldTypeParser,
1093
+ ]);
1094
+
1095
+ setParserName(smaliShortyReturnTypeParser, 'smaliShortyReturnTypeParser');
1096
+
1097
+ function shortyGetInsSize(shorty: string): number {
1098
+ let size = 0;
1099
+
1100
+ for (const char of shorty.slice(1)) {
1101
+ if (char === 'J' || char === 'D') {
1102
+ size += 2;
1103
+ } else if (char !== 'V') {
1104
+ size += 1;
1105
+ }
1106
+ }
1107
+
1108
+ return size;
1109
+ }
1110
+
1111
+ const smaliCodeRegistersParser: Parser<number, string> = promiseCompose(
1112
+ createTupleParser([
1113
+ createExactSequenceParser(' .registers '),
1114
+ smaliNumberParser,
1115
+ smaliLineEndPraser,
1116
+ ]),
1117
+ ([
1118
+ _registers,
1119
+ registers,
1120
+ _newline,
1121
+ ]) => registers,
1122
+ );
1123
+
1124
+ setParserName(smaliCodeRegistersParser, 'smaliCodeRegistersParser');
1125
+
492
1126
  const smaliCodeLineParser: Parser<number, string> = promiseCompose(
493
1127
  createTupleParser([
494
1128
  createExactSequenceParser(' .line '),
495
- jsonNumberParser,
496
- createExactSequenceParser('\n'),
1129
+ smaliNumberParser,
1130
+ smaliLineEndPraser,
497
1131
  ]),
498
1132
  ([
499
1133
  _line,
@@ -512,11 +1146,11 @@ type SmaliRegister = {
512
1146
  function isSmaliRegister(value: unknown): value is SmaliRegister {
513
1147
  return (
514
1148
  value !== null
515
- && typeof value === 'object'
516
- && 'prefix' in value
517
- && 'index' in value
518
- && typeof (value as SmaliRegister).prefix === 'string'
519
- && typeof (value as SmaliRegister).index === 'number'
1149
+ && typeof value === 'object'
1150
+ && 'prefix' in value
1151
+ && 'index' in value
1152
+ && typeof (value as SmaliRegister).prefix === 'string'
1153
+ && typeof (value as SmaliRegister).index === 'number'
520
1154
  );
521
1155
  }
522
1156
 
@@ -524,11 +1158,11 @@ const smaliParametersRegisterParser: Parser<SmaliRegister, string> = promiseComp
524
1158
  createUnionParser<['v' | 'p', number], string>([
525
1159
  createTupleParser([
526
1160
  createExactSequenceParser('v'),
527
- jsonNumberParser,
1161
+ smaliNumberParser,
528
1162
  ]),
529
1163
  createTupleParser([
530
1164
  createExactSequenceParser('p'),
531
- jsonNumberParser,
1165
+ smaliNumberParser,
532
1166
  ]),
533
1167
  ]),
534
1168
  ([
@@ -542,85 +1176,297 @@ const smaliParametersRegisterParser: Parser<SmaliRegister, string> = promiseComp
542
1176
 
543
1177
  setParserName(smaliParametersRegisterParser, 'smaliParametersRegisterParser');
544
1178
 
545
- const smaliCodeLocalParser: Parser<SmaliRegister, string> = promiseCompose(
1179
+ type SmaliCodeLocal = {
1180
+ register: SmaliRegister;
1181
+ name: string | undefined;
1182
+ type: string | undefined;
1183
+ };
1184
+
1185
+ const smaliCodeLocalParser: Parser<SmaliCodeLocal, string> = promiseCompose(
546
1186
  createTupleParser([
547
1187
  createExactSequenceParser(' .local '),
548
1188
  smaliParametersRegisterParser,
549
- createOptionalParser(
550
- createTupleParser([
551
- createExactSequenceParser(','),
552
- createArrayParser(createExactSequenceParser(' ')),
553
- jsonStringParser,
554
- createExactSequenceParser(':'),
555
- smaliTypeDescriptorParser,
556
- ]),
557
- ),
558
- createExactSequenceParser('\n'),
1189
+ createOptionalParser(createTupleParser([
1190
+ createExactSequenceParser(','),
1191
+ smaliWhitespaceParser,
1192
+ smaliQuotedStringParser,
1193
+ createExactSequenceParser(':'),
1194
+ smaliTypeDescriptorParser,
1195
+ ])),
1196
+ smaliLineEndPraser,
559
1197
  ]),
560
1198
  ([
561
1199
  _local,
562
- local,
563
- _newlocal,
564
- ]) => local,
1200
+ register,
1201
+ nameAndType,
1202
+ ]) => ({
1203
+ register,
1204
+ name: nameAndType?.[2],
1205
+ type: nameAndType?.[4],
1206
+ }),
565
1207
  );
566
1208
 
567
1209
  setParserName(smaliCodeLocalParser, 'smaliCodeLocalParser');
568
1210
 
569
- export const smaliCodeParameterParser: Parser<SmaliRegister, string> = promiseCompose(
1211
+ const smaliCodeEndLocalParser: Parser<SmaliRegister, string> = promiseCompose(
1212
+ createTupleParser([
1213
+ createExactSequenceParser(' .end local '),
1214
+ smaliParametersRegisterParser,
1215
+ smaliLineEndPraser,
1216
+ ]),
1217
+ ([
1218
+ _endLocal,
1219
+ register,
1220
+ _newline,
1221
+ ]) => register,
1222
+ );
1223
+
1224
+ setParserName(smaliCodeEndLocalParser, 'smaliCodeEndLocalParser');
1225
+
1226
+ const smaliCodeRestartLocalParser: Parser<SmaliRegister, string> = promiseCompose(
1227
+ createTupleParser([
1228
+ createExactSequenceParser(' .restart local '),
1229
+ smaliParametersRegisterParser,
1230
+ smaliLineEndPraser,
1231
+ ]),
1232
+ ([
1233
+ _restartLocal,
1234
+ register,
1235
+ _newline,
1236
+ ]) => register,
1237
+ );
1238
+
1239
+ setParserName(smaliCodeRestartLocalParser, 'smaliCodeRestartLocalParser');
1240
+
1241
+ const smaliCodePrologueEndParser: Parser<true, string> = promiseCompose(
1242
+ createTupleParser([
1243
+ createExactSequenceParser(' .prologue'),
1244
+ smaliLineEndPraser,
1245
+ ]),
1246
+ () => true as const,
1247
+ );
1248
+
1249
+ setParserName(smaliCodePrologueEndParser, 'smaliCodePrologueEndParser');
1250
+
1251
+ const smaliCodeEpilogueBeginParser: Parser<true, string> = promiseCompose(
1252
+ createTupleParser([
1253
+ createExactSequenceParser(' .epilogue'),
1254
+ smaliLineEndPraser,
1255
+ ]),
1256
+ () => true as const,
1257
+ );
1258
+
1259
+ setParserName(smaliCodeEpilogueBeginParser, 'smaliCodeEpilogueBeginParser');
1260
+
1261
+ type SmaliCodeParameter = {
1262
+ register: SmaliRegister;
1263
+ name: string | undefined;
1264
+ annotation: SmaliAnnotation | undefined;
1265
+ };
1266
+
1267
+ export const smaliCodeParameterParser: Parser<SmaliCodeParameter, string> = promiseCompose(
570
1268
  createTupleParser([
571
1269
  createExactSequenceParser(' .param '),
572
1270
  smaliParametersRegisterParser,
573
- createOptionalParser(
574
- createTupleParser([
575
- createExactSequenceParser(','),
576
- createArrayParser(createExactSequenceParser(' ')),
577
- jsonStringParser,
578
- createArrayParser(createExactSequenceParser(' ')),
579
- ]),
580
- ),
581
- createOptionalParser(smaliCommentParser),
582
- createExactSequenceParser('\n'),
1271
+ createOptionalParser(createTupleParser([
1272
+ createExactSequenceParser(','),
1273
+ smaliWhitespaceParser,
1274
+ smaliQuotedStringParser,
1275
+ smaliWhitespaceParser,
1276
+ ])),
1277
+ createOptionalParser(smaliWhitespaceParser),
1278
+ smaliCommentsOrNewlinesParser,
1279
+ createOptionalParser(createTupleParser([
1280
+ smaliAnnotationParser,
1281
+ smaliIndentationParser,
1282
+ createExactSequenceParser('.end param\n'),
1283
+ ])),
583
1284
  ]),
584
1285
  ([
585
- _param,
586
- parameter,
587
- _newparam,
1286
+ _parameter,
1287
+ register,
1288
+ optionalCommaAndString,
1289
+ _whitespace,
588
1290
  _commentOrNewline,
589
- ]) => parameter,
1291
+ optionalAnnotation,
1292
+ ]) => {
1293
+ let annotation: undefined | SmaliAnnotation;
1294
+
1295
+ if (optionalAnnotation) {
1296
+ annotation = optionalAnnotation[0];
1297
+ }
1298
+
1299
+ return {
1300
+ register,
1301
+ name: optionalCommaAndString?.[2],
1302
+ annotation,
1303
+ };
1304
+ },
590
1305
  );
591
1306
 
592
1307
  setParserName(smaliCodeParameterParser, 'smaliCodeParameterParser');
593
1308
 
594
1309
  const smaliCodeLabelParser: Parser<string, string> = promiseCompose(
595
1310
  createTupleParser([
596
- createExactSequenceParser(' :'),
1311
+ createExactSequenceParser(':'),
597
1312
  smaliIdentifierParser,
598
- createExactSequenceParser('\n'),
599
1313
  ]),
600
1314
  ([
601
- _label,
602
- label,
603
- _newlabel,
604
- ]) => label,
1315
+ _label,
1316
+ label,
1317
+ ]) => label,
1318
+ );
1319
+
1320
+ setParserName(smaliCodeLabelParser, 'smaliCodeLabelParser');
1321
+
1322
+ const smaliCodeLabelLineParser: Parser<string, string> = promiseCompose(
1323
+ createTupleParser([
1324
+ smaliIndentationParser,
1325
+ smaliCodeLabelParser,
1326
+ smaliLineEndPraser,
1327
+ ]),
1328
+ ([
1329
+ _label,
1330
+ label,
1331
+ _newlabel,
1332
+ ]) => label,
1333
+ );
1334
+
1335
+ setParserName(smaliCodeLabelLineParser, 'smaliCodeLabelLineParser');
1336
+
1337
+ type SmaliCatchDirective = {
1338
+ type: string | undefined; // undefined for .catchall
1339
+ startLabel: string;
1340
+ endLabel: string;
1341
+ handlerLabel: string;
1342
+ };
1343
+
1344
+ const smaliCatchDirectiveParser: Parser<SmaliCatchDirective, string> = promiseCompose(
1345
+ createTupleParser([
1346
+ smaliIndentationParser,
1347
+ createExactSequenceParser('.catch'),
1348
+ createUnionParser<string | undefined, string, string>([
1349
+ promiseCompose(
1350
+ createExactSequenceParser('all'),
1351
+ () => undefined as undefined,
1352
+ ),
1353
+ promiseCompose(
1354
+ createTupleParser([
1355
+ createExactSequenceParser(' '),
1356
+ smaliTypeDescriptorParser,
1357
+ ]),
1358
+ ([
1359
+ _space,
1360
+ type,
1361
+ ]) => type,
1362
+ ),
1363
+ ]),
1364
+ createExactSequenceParser(' {'),
1365
+ smaliCodeLabelParser,
1366
+ createExactSequenceParser(' .. '),
1367
+ smaliCodeLabelParser,
1368
+ createExactSequenceParser('} '),
1369
+ smaliCodeLabelParser,
1370
+ smaliLineEndPraser,
1371
+ ]),
1372
+ ([
1373
+ _indentation,
1374
+ _catch,
1375
+ type,
1376
+ _openBrace,
1377
+ startLabel,
1378
+ _dots,
1379
+ endLabel,
1380
+ _closeBrace,
1381
+ handlerLabel,
1382
+ _newline,
1383
+ ]) => ({
1384
+ type,
1385
+ startLabel,
1386
+ endLabel,
1387
+ handlerLabel,
1388
+ }),
1389
+ );
1390
+
1391
+ setParserName(smaliCatchDirectiveParser, 'smaliCatchDirectiveParser');
1392
+
1393
+ type SmaliLabeledCatchDirective = {
1394
+ labels: string[];
1395
+ catchDirective: SmaliCatchDirective;
1396
+ };
1397
+
1398
+ const smaliLabeledCatchDirectiveParser: Parser<SmaliLabeledCatchDirective, string> = promiseCompose(
1399
+ createTupleParser([
1400
+ createArrayParser(smaliCodeLineParser),
1401
+ createOptionalParser(smaliCodeLocalParser),
1402
+ createArrayParser(smaliCodeLabelLineParser),
1403
+ smaliCatchDirectiveParser,
1404
+ ]),
1405
+ ([
1406
+ _lines,
1407
+ _local,
1408
+ labels,
1409
+ catchDirective,
1410
+ ]) => ({
1411
+ labels,
1412
+ catchDirective,
1413
+ }),
1414
+ );
1415
+
1416
+ setParserName(smaliLabeledCatchDirectiveParser, 'smaliLabeledCatchDirectiveParser');
1417
+
1418
+ const smaliParametersRegisterRangeParser: Parser<SmaliRegister[], string> = promiseCompose(
1419
+ createTupleParser([
1420
+ createExactSequenceParser('{'),
1421
+ smaliParametersRegisterParser,
1422
+ createExactSequenceParser(' .. '),
1423
+ smaliParametersRegisterParser,
1424
+ createExactSequenceParser('}'),
1425
+ ]),
1426
+ ([
1427
+ _openBrace,
1428
+ startRegister,
1429
+ _dotDot,
1430
+ endRegister,
1431
+ _closeBrace,
1432
+ ]) => {
1433
+ invariant(
1434
+ startRegister.prefix === endRegister.prefix,
1435
+ 'Register range must use the same prefix',
1436
+ );
1437
+
1438
+ invariant(
1439
+ startRegister.index <= endRegister.index,
1440
+ 'Register range start must be less than or equal to end',
1441
+ );
1442
+
1443
+ const registers: SmaliRegister[] = [];
1444
+ for (let i = startRegister.index; i <= endRegister.index; i++) {
1445
+ registers.push({
1446
+ prefix: startRegister.prefix,
1447
+ index: i,
1448
+ });
1449
+ }
1450
+
1451
+ return registers;
1452
+ },
605
1453
  );
606
1454
 
607
- setParserName(smaliCodeLabelParser, 'smaliCodeLabelParser');
1455
+ setParserName(smaliParametersRegisterRangeParser, 'smaliParametersRegisterRangeParser');
608
1456
 
609
- const smaliParametersRegistersParser: Parser<SmaliRegister[], string> = promiseCompose(
1457
+ const smaliParametersRegisterListParser: Parser<SmaliRegister[], string> = promiseCompose(
610
1458
  createTupleParser([
611
1459
  createExactSequenceParser('{'),
612
- createArrayParser(
613
- promiseCompose(
614
- createTupleParser([
615
- smaliParametersRegisterParser,
616
- createOptionalParser(createExactSequenceParser(', ')),
617
- ]),
618
- ([
619
- parameter,
620
- _comma,
621
- ]) => parameter,
622
- ),
623
- ),
1460
+ createArrayParser(promiseCompose(
1461
+ createTupleParser([
1462
+ smaliParametersRegisterParser,
1463
+ createOptionalParser(createExactSequenceParser(', ')),
1464
+ ]),
1465
+ ([
1466
+ parameter,
1467
+ _comma,
1468
+ ]) => parameter,
1469
+ )),
624
1470
  createExactSequenceParser('}'),
625
1471
  ]),
626
1472
  ([
@@ -630,6 +1476,13 @@ const smaliParametersRegistersParser: Parser<SmaliRegister[], string> = promiseC
630
1476
  ]) => parameters,
631
1477
  );
632
1478
 
1479
+ setParserName(smaliParametersRegisterListParser, 'smaliParametersRegisterListParser');
1480
+
1481
+ const smaliParametersRegistersParser: Parser<SmaliRegister[], string> = createUnionParser([
1482
+ smaliParametersRegisterRangeParser,
1483
+ smaliParametersRegisterListParser,
1484
+ ]);
1485
+
633
1486
  setParserName(smaliParametersRegistersParser, 'smaliParametersRegistersParser');
634
1487
 
635
1488
  const smaliParametersStringParser: Parser<string, string> = cloneParser(smaliQuotedStringParser);
@@ -638,6 +1491,7 @@ setParserName(smaliParametersStringParser, 'smaliParametersStringParser');
638
1491
 
639
1492
  const smaliParametersIntegerParser: Parser<number | bigint, string> = promiseCompose(
640
1493
  createTupleParser([
1494
+ createOptionalParser(createExactSequenceParser('-')),
641
1495
  createExactSequenceParser('0x'),
642
1496
  async parserContext => {
643
1497
  const characters: string[] = [];
@@ -651,8 +1505,8 @@ const smaliParametersIntegerParser: Parser<number | bigint, string> = promiseCom
651
1505
 
652
1506
  if (
653
1507
  (character >= '0' && character <= '9')
654
- || (character >= 'a' && character <= 'f')
655
- || (character >= 'A' && character <= 'F')
1508
+ || (character >= 'a' && character <= 'f')
1509
+ || (character >= 'A' && character <= 'F')
656
1510
  ) {
657
1511
  characters.push(character);
658
1512
 
@@ -666,18 +1520,27 @@ const smaliParametersIntegerParser: Parser<number | bigint, string> = promiseCom
666
1520
 
667
1521
  return characters.join('');
668
1522
  },
669
- createOptionalParser(createExactSequenceParser('L')),
1523
+ createOptionalParser(createUnionParser([
1524
+ createExactSequenceParser('L'),
1525
+ createExactSequenceParser('t'),
1526
+ createExactSequenceParser('s'),
1527
+ ])),
670
1528
  ]),
671
1529
  ([
1530
+ optionalMinus,
672
1531
  _0x,
673
1532
  value,
674
- optionalL,
1533
+ optionalSuffix,
675
1534
  ]) => {
676
- if (optionalL) {
677
- return BigInt('0x' + value);
1535
+ if (optionalSuffix === 'L') {
1536
+ const sign = optionalMinus ? -1n : 1n;
1537
+
1538
+ return sign * BigInt('0x' + value);
678
1539
  }
679
1540
 
680
- return parseInt(value, 16);
1541
+ const sign = optionalMinus ? -1 : 1;
1542
+
1543
+ return sign * Number.parseInt(value, 16);
681
1544
  },
682
1545
  );
683
1546
 
@@ -700,27 +1563,6 @@ const smaliParametersTypeParser: Parser<string, string> = cloneParser(smaliTypeD
700
1563
 
701
1564
  setParserName(smaliParametersTypeParser, 'smaliParametersTypeParser');
702
1565
 
703
- const smaliParametersMethodParser: Parser<DalvikExecutableMethod, string> = promiseCompose(
704
- createTupleParser([
705
- smaliTypeDescriptorParser,
706
- createExactSequenceParser('->'),
707
- smaliMemberNameParser,
708
- smaliMethodPrototypeParser,
709
- ]),
710
- ([
711
- classPath,
712
- _separator,
713
- methodName,
714
- prototype,
715
- ]) => ({
716
- class: classPath,
717
- prototype,
718
- name: methodName,
719
- })
720
- );
721
-
722
- setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
723
-
724
1566
  const smaliParametersFieldParser: Parser<DalvikExecutableField, string> = promiseCompose(
725
1567
  createTupleParser([
726
1568
  smaliTypeDescriptorParser,
@@ -739,7 +1581,7 @@ const smaliParametersFieldParser: Parser<DalvikExecutableField, string> = promis
739
1581
  class: classPath,
740
1582
  name: fieldName,
741
1583
  type,
742
- })
1584
+ }),
743
1585
  );
744
1586
 
745
1587
  setParserName(smaliParametersFieldParser, 'smaliParametersFieldParser');
@@ -751,38 +1593,43 @@ type SmaliCodeOperationParameter =
751
1593
  | DalvikExecutableMethod
752
1594
  ;
753
1595
 
754
- const smaliCodeOperationParametersParser: Parser<SmaliCodeOperationParameter[], string> = createArrayParser(
755
- promiseCompose(
756
- createTupleParser([
757
- createUnionParser<SmaliCodeOperationParameter, string>([
758
- smaliParametersRegisterParser,
759
- smaliParametersRegistersParser,
760
- smaliParametersStringParser,
761
- smaliParametersIntegerParser,
762
- smaliParametersLabelParser,
763
- promiseCompose(
764
- createTupleParser([
765
- createNegativeLookaheadParser(smaliParametersMethodParser),
766
- createNegativeLookaheadParser(smaliParametersFieldParser),
767
- smaliParametersTypeParser,
768
- ]),
769
- ([
770
- _notMethod,
771
- _notField,
772
- type,
773
- ]) => type,
774
- ),
775
- smaliParametersMethodParser,
776
- smaliParametersFieldParser,
777
- ]),
778
- createOptionalParser(createExactSequenceParser(', ')),
1596
+ const smaliCodeOperationParametersParser: Parser<SmaliCodeOperationParameter[], string> = createArrayParser(promiseCompose(
1597
+ createTupleParser([
1598
+ createUnionParser<SmaliCodeOperationParameter, string>([
1599
+ smaliParametersRegisterParser,
1600
+ smaliParametersRegistersParser,
1601
+ smaliParametersStringParser,
1602
+ smaliParametersIntegerParser,
1603
+ promiseCompose(
1604
+ createTupleParser([
1605
+ createNegativeLookaheadParser(smaliParametersIntegerParser),
1606
+ smaliNumberParser,
1607
+ ]),
1608
+ ([ _notInteger, number ]) => number,
1609
+ ),
1610
+ smaliParametersLabelParser,
1611
+ promiseCompose(
1612
+ createTupleParser([
1613
+ createNegativeLookaheadParser(smaliParametersMethodParser),
1614
+ createNegativeLookaheadParser(smaliParametersFieldParser),
1615
+ smaliParametersTypeParser,
1616
+ ]),
1617
+ ([
1618
+ _notMethod,
1619
+ _notField,
1620
+ type,
1621
+ ]) => type,
1622
+ ),
1623
+ smaliParametersMethodParser,
1624
+ smaliParametersFieldParser,
779
1625
  ]),
780
- ([
781
- parameter,
782
- _comma,
783
- ]) => parameter,
784
- ),
785
- );
1626
+ createOptionalParser(createExactSequenceParser(', ')),
1627
+ ]),
1628
+ ([
1629
+ parameter,
1630
+ _comma,
1631
+ ]) => parameter,
1632
+ ));
786
1633
 
787
1634
  setParserName(smaliCodeOperationParametersParser, 'smaliCodeOperationParametersParser');
788
1635
 
@@ -793,17 +1640,15 @@ const smaliCodeOperationNameParser: Parser<string, string> = promiseCompose(
793
1640
  smaliIdentifierParser,
794
1641
  createExactSequenceParser('-'),
795
1642
  ),
796
- (parts) => parts.join('-'),
797
- ),
798
- createOptionalParser(
799
- promiseCompose(
800
- createTupleParser([
801
- createExactSequenceParser('/'),
802
- smaliIdentifierParser,
803
- ]),
804
- ([ slash, name ]) => slash + name,
805
- ),
1643
+ parts => parts.join('-'),
806
1644
  ),
1645
+ createOptionalParser(promiseCompose(
1646
+ createTupleParser([
1647
+ createExactSequenceParser('/'),
1648
+ smaliIdentifierParser,
1649
+ ]),
1650
+ ([ slash, name ]) => slash + name,
1651
+ )),
807
1652
  ]),
808
1653
  ([
809
1654
  name,
@@ -813,18 +1658,23 @@ const smaliCodeOperationNameParser: Parser<string, string> = promiseCompose(
813
1658
 
814
1659
  setParserName(smaliCodeOperationNameParser, 'smaliCodeOperationNameParser');
815
1660
 
816
- const smaliCodeOperationParser: Parser<DalvikBytecodeOperation, string> = promiseCompose(
1661
+ type SmaliOneLineCodeOperation = {
1662
+ operation: string;
1663
+ parameters: SmaliCodeOperationParameter[];
1664
+ };
1665
+
1666
+ const smaliOneLineCodeOperationParser: Parser<SmaliOneLineCodeOperation, string> = promiseCompose(
817
1667
  createTupleParser([
818
- createExactSequenceParser(' '),
1668
+ smaliSingleIndentationParser,
819
1669
  smaliCodeOperationNameParser,
820
1670
  promiseCompose(
821
1671
  createOptionalParser(createTupleParser([
822
- createExactSequenceParser(' '),
1672
+ smaliSingleWhitespaceParser,
823
1673
  smaliCodeOperationParametersParser,
824
1674
  ])),
825
- (undefinedOrParameters) => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1],
1675
+ undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1],
826
1676
  ),
827
- createExactSequenceParser('\n'),
1677
+ smaliLineEndPraser,
828
1678
  ]),
829
1679
  ([
830
1680
  _indent,
@@ -834,223 +1684,1190 @@ const smaliCodeOperationParser: Parser<DalvikBytecodeOperation, string> = promis
834
1684
  ]) => ({
835
1685
  operation,
836
1686
  parameters,
837
- } as any), // TODO
1687
+ }),
1688
+ );
1689
+
1690
+ setParserName(smaliOneLineCodeOperationParser, 'smaliOneLineCodeOperationParser');
1691
+
1692
+ type SmaliCodeOperationLabelsBody = string[];
1693
+
1694
+ const createMultilineSmaliCodeOperationLabelsBodyParser: (operationName: string) => Parser<SmaliCodeOperationLabelsBody, string> = operationName => promiseCompose(
1695
+ createTupleParser([
1696
+ createArrayParser(promiseCompose(
1697
+ createTupleParser([
1698
+ smaliIndentationParser,
1699
+ smaliCodeLabelParser,
1700
+ smaliLineEndPraser,
1701
+ ]),
1702
+ ([
1703
+ _indentation,
1704
+ label,
1705
+ _newline,
1706
+ ]) => label,
1707
+ )),
1708
+ smaliIndentationParser,
1709
+ createExactSequenceParser('.end '),
1710
+ createExactSequenceParser(operationName),
1711
+ smaliLineEndPraser,
1712
+ ]),
1713
+ ([
1714
+ labels,
1715
+ ]) => labels,
1716
+ );
1717
+
1718
+ type SmaliCodeOperationLabelMapBody = Array<readonly [number | bigint, string]>;
1719
+
1720
+ const createMultilineSmaliCodeOperationLabelMapBodyParser: (operationName: string) => Parser<SmaliCodeOperationLabelMapBody, string> = operationName => promiseCompose(
1721
+ createTupleParser([
1722
+ createArrayParser(promiseCompose(
1723
+ createTupleParser([
1724
+ smaliIndentationParser,
1725
+ smaliParametersIntegerParser,
1726
+ createExactSequenceParser(' -> '),
1727
+ smaliCodeLabelParser,
1728
+ smaliLineEndPraser,
1729
+ ]),
1730
+ ([
1731
+ _indentation,
1732
+ key,
1733
+ _arrow,
1734
+ label,
1735
+ _newline,
1736
+ ]) => [
1737
+ key,
1738
+ label,
1739
+ ] as const,
1740
+ )),
1741
+ smaliIndentationParser,
1742
+ createExactSequenceParser('.end '),
1743
+ createExactSequenceParser(operationName),
1744
+ smaliLineEndPraser,
1745
+ ]),
1746
+ ([
1747
+ labels,
1748
+ ]) => labels,
1749
+ );
1750
+
1751
+ type SmaliCodeOperationIntegersBody = Array<number | bigint>;
1752
+
1753
+ const createMultilineSmaliCodeOperationIntegersBodyParser: (operationName: string) => Parser<SmaliCodeOperationIntegersBody, string> = operationName => promiseCompose(
1754
+ createTupleParser([
1755
+ createArrayParser(promiseCompose(
1756
+ createTupleParser([
1757
+ smaliIndentationParser,
1758
+ smaliParametersIntegerParser,
1759
+ smaliLineEndPraser,
1760
+ ]),
1761
+ ([
1762
+ _indentation,
1763
+ key,
1764
+ ]) => key,
1765
+ )),
1766
+ smaliIndentationParser,
1767
+ createExactSequenceParser('.end '),
1768
+ createExactSequenceParser(operationName),
1769
+ smaliLineEndPraser,
1770
+ ]),
1771
+ ([
1772
+ labels,
1773
+ ]) => labels,
1774
+ );
1775
+
1776
+ type SmaliCodeOperationBody =
1777
+ | SmaliCodeOperationLabelsBody
1778
+ | SmaliCodeOperationIntegersBody
1779
+ | SmaliCodeOperationLabelMapBody
1780
+ ;
1781
+
1782
+ const createMultilineSmaliCodeOperationBodyParser: (operationName: string) => Parser<SmaliCodeOperationBody, string> = operationName => createUnionParser([
1783
+ createMultilineSmaliCodeOperationLabelsBodyParser(operationName),
1784
+ createMultilineSmaliCodeOperationIntegersBodyParser(operationName),
1785
+ createMultilineSmaliCodeOperationLabelMapBodyParser(operationName),
1786
+ ]);
1787
+
1788
+ type SmaliMultilineCodeOperation = {
1789
+ operation: string;
1790
+ parameters: SmaliCodeOperationParameter[];
1791
+ body: SmaliCodeOperationBody;
1792
+ };
1793
+
1794
+ const smaliMultilineCodeOperationParser: Parser<SmaliMultilineCodeOperation, string> = parserCreatorCompose(
1795
+ () => promiseCompose(
1796
+ createTupleParser([
1797
+ smaliSingleIndentationParser,
1798
+ createExactSequenceParser('.'),
1799
+ smaliCodeOperationNameParser,
1800
+ promiseCompose(
1801
+ createOptionalParser(createTupleParser([
1802
+ smaliSingleWhitespaceParser,
1803
+ smaliCodeOperationParametersParser,
1804
+ ])),
1805
+ undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1],
1806
+ ),
1807
+ smaliLineEndPraser,
1808
+ ]),
1809
+ ([
1810
+ _indent,
1811
+ _dot,
1812
+ operation,
1813
+ parameters,
1814
+ _newline,
1815
+ ]) => ({
1816
+ operation,
1817
+ parameters,
1818
+ }),
1819
+ ),
1820
+ ({ operation, parameters }) => promiseCompose(
1821
+ createMultilineSmaliCodeOperationBodyParser(operation),
1822
+ body => ({
1823
+ operation,
1824
+ parameters,
1825
+ body,
1826
+ }),
1827
+ ),
1828
+ )();
1829
+
1830
+ setParserName(smaliMultilineCodeOperationParser, 'smaliMultilineCodeOperationParser');
1831
+
1832
+ type SmaliLooseCodeOperation =
1833
+ | SmaliOneLineCodeOperation
1834
+ | SmaliMultilineCodeOperation
1835
+ ;
1836
+
1837
+ const smaliLooseCodeOperationParser: Parser<SmaliLooseCodeOperation, string> = createUnionParser([
1838
+ smaliOneLineCodeOperationParser,
1839
+ smaliMultilineCodeOperationParser,
1840
+ ]);
1841
+
1842
+ setParserName(smaliLooseCodeOperationParser, 'smaliLooseCodeOperationParser');
1843
+
1844
+ type SmaliCodeOperationFromDalvikBytecodeOperation<T extends DalvikBytecodeOperation> =
1845
+ T extends { branchOffsets: number[] }
1846
+ ? Simplify<Omit<T, 'branchOffsets'> & { branchOffsetIndices: number[] }>
1847
+ : T extends { branchOffset: number }
1848
+ ? Simplify<Omit<T, 'branchOffset'> & { branchOffsetIndex: number }>
1849
+ : T extends { methodIndex: IndexIntoMethodIds }
1850
+ ? Simplify<Omit<T, 'methodIndex'> & { method: DalvikExecutableMethod }>
1851
+ : T
1852
+ ;
1853
+
1854
+ type SmaliCodeOperation = SmaliCodeOperationFromDalvikBytecodeOperation<DalvikBytecodeOperation>;
1855
+
1856
+ export const smaliCodeOperationParser: Parser<SmaliCodeOperation, string> = promiseCompose(
1857
+ smaliLooseCodeOperationParser,
1858
+ operation => {
1859
+ const operation_ = {
1860
+ operation: operation.operation,
1861
+ // Line,
1862
+ // local,
1863
+ // parameters: (operation as any).parameters,
1864
+ } as any; // TODO
1865
+
1866
+ if ('body' in operation) {
1867
+ const operationDexName = payloadOperationSmaliNameToDexName.get(operation.operation);
1868
+
1869
+ invariant(operationDexName, 'Unknown payload operation for %s', operation.operation);
1870
+
1871
+ operation_.operation = operationDexName;
1872
+
1873
+ if (operationDexName === 'sparse-switch-payload') {
1874
+ const map = new Map<number | bigint, string>(operation.body as SmaliCodeOperationLabelMapBody);
1875
+ operation_.keys = [ ...map.keys() ];
1876
+ operation_.branchLabels = [ ...map.values() ];
1877
+ } else if (operationDexName === 'fill-array-data-payload') {
1878
+ // Get elementWidth from parameters (first parameter)
1879
+ const elementWidth = operation.parameters[0];
1880
+ invariant(typeof elementWidth === 'number', 'Expected elementWidth to be a number');
1881
+
1882
+ operation_.elementWidth = elementWidth;
1883
+
1884
+ // Convert integer values to bytes (little-endian)
1885
+ const values = operation.body as SmaliCodeOperationIntegersBody;
1886
+ const data: number[] = [];
1887
+
1888
+ for (const value of values) {
1889
+ // Use BigInt for bitwise operations to preserve full precision
1890
+ const bigIntValue = typeof value === 'bigint' ? value : BigInt(value);
1891
+
1892
+ // Convert to bytes based on elementWidth (little-endian)
1893
+ for (let i = 0; i < elementWidth; i++) {
1894
+ data.push(Number((bigIntValue >> BigInt(i * 8)) & 0xFFn));
1895
+ }
1896
+ }
1897
+
1898
+ operation_.data = data;
1899
+ } else {
1900
+ operation_.branchLabels = operation.body;
1901
+ }
1902
+ }
1903
+
1904
+ const hasRegisters = Array.isArray(operation.parameters.at(0));
1905
+ if (hasRegisters) {
1906
+ operation_.registers = [];
1907
+ }
1908
+
1909
+ for (const parameter of (operation as unknown as { parameters: unknown[] }).parameters.flat() ?? []) {
1910
+ if (isDalvikExecutableMethod(parameter)) {
1911
+ operation_.method = parameter;
1912
+
1913
+ continue;
1914
+ }
1915
+
1916
+ if (isDalvikExecutableField(parameter)) {
1917
+ operation_.field = parameter;
1918
+
1919
+ continue;
1920
+ }
1921
+
1922
+ if (isSmaliRegister(parameter)) {
1923
+ operation_.registers ||= [];
1924
+
1925
+ operation_.registers.push(parameter);
1926
+
1927
+ continue;
1928
+ }
1929
+
1930
+ if (typeof parameter === 'string') {
1931
+ if (operationsWithTypeArgument.has(operation_.operation)) {
1932
+ operation_.type = parameter;
1933
+
1934
+ continue;
1935
+ }
1936
+
1937
+ if (operationsWithBranchLabelArgument.has(operation_.operation)) {
1938
+ operation_.branchLabel = parameter;
1939
+
1940
+ continue;
1941
+ }
1942
+
1943
+ operation_.string = parameter;
1944
+
1945
+ continue;
1946
+ }
1947
+
1948
+ if (typeof parameter === 'number' || typeof parameter === 'bigint') {
1949
+ // Skip for fill-array-data-payload, already handled in body processing
1950
+ if (operation_.operation === 'fill-array-data-payload') {
1951
+ continue;
1952
+ }
1953
+
1954
+ // Const-wide operations always use bigint values
1955
+ if (operationsWithBigintValue.has(operation_.operation)) {
1956
+ operation_.value = typeof parameter === 'number' ? BigInt(parameter) : parameter;
1957
+ } else {
1958
+ operation_.value = parameter;
1959
+ }
1960
+
1961
+ continue;
1962
+ }
1963
+
1964
+ invariant(false, 'TODO: parameter: %s, operation: %s', JSON.stringify(parameter), JSON.stringify(operation));
1965
+ }
1966
+
1967
+ return operation_;
1968
+ },
838
1969
  );
839
1970
 
840
1971
  setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
841
1972
 
842
1973
  const operationsWithTypeArgument = new Set<DalvikBytecodeOperation['operation']>([
843
1974
  'new-instance',
1975
+ 'new-array',
1976
+ 'filled-new-array',
1977
+ 'filled-new-array/range',
844
1978
  'check-cast',
845
1979
  'instance-of',
1980
+ 'const-class',
1981
+ ]);
1982
+
1983
+ const operationsWithBigintValue = new Set<DalvikBytecodeOperation['operation']>([
1984
+ 'const-wide/16',
1985
+ 'const-wide/32',
1986
+ 'const-wide',
1987
+ ]);
1988
+
1989
+ const operationsWithBranchLabelArgument = new Set<DalvikBytecodeOperation['operation']>([
1990
+ 'goto',
1991
+ 'goto/16',
1992
+ 'goto/32',
1993
+ 'if-eq',
1994
+ 'if-ne',
1995
+ 'if-lt',
1996
+ 'if-ge',
1997
+ 'if-gt',
1998
+ 'if-le',
1999
+ 'if-eqz',
2000
+ 'if-nez',
2001
+ 'if-ltz',
2002
+ 'if-gez',
2003
+ 'if-gtz',
2004
+ 'if-lez',
2005
+ 'packed-switch',
2006
+ 'sparse-switch',
2007
+ 'fill-array-data',
846
2008
  ]);
847
2009
 
848
- const smaliAnnotatedCodeOperationParser: Parser<DalvikBytecodeOperation, string> = promiseCompose(
2010
+ const payloadOperationSmaliNameToDexName = new Map<string, string>(Object.entries({
2011
+ 'packed-switch': 'packed-switch-payload',
2012
+ 'sparse-switch': 'sparse-switch-payload',
2013
+ 'array-data': 'fill-array-data-payload',
2014
+ }));
2015
+
2016
+ function isOperationWithLabels(value: unknown): value is DalvikBytecodeOperation & {
2017
+ labels: Set<string>;
2018
+ } {
2019
+ return (
2020
+ typeof value === 'object'
2021
+ && value !== null
2022
+ && 'labels' in value
2023
+ && value.labels instanceof Set
2024
+ );
2025
+ }
2026
+
2027
+ type SmaliDebugDirective =
2028
+ | { type: 'line'; line: number }
2029
+ | { type: 'startLocal'; local: SmaliCodeLocal }
2030
+ | { type: 'endLocal'; register: SmaliRegister }
2031
+ | { type: 'restartLocal'; register: SmaliRegister }
2032
+ | { type: 'setPrologueEnd' }
2033
+ | { type: 'setEpilogueBegin' };
2034
+
2035
+ type SmaliAnnotatedCodeOperation = {
2036
+ debugDirectives: SmaliDebugDirective[];
2037
+ labels: string[];
2038
+ operation: SmaliCodeOperation;
2039
+ };
2040
+
2041
+ const smaliAnnotatedCodeOperationParser: Parser<SmaliAnnotatedCodeOperation, string> = promiseCompose(
849
2042
  createTupleParser([
850
2043
  createArrayParser(smaliCodeLineParser),
851
2044
  createOptionalParser(smaliCodeLocalParser),
852
- createArrayParser(smaliCodeLabelParser),
2045
+ createOptionalParser(smaliCodeEndLocalParser),
2046
+ createOptionalParser(smaliCodeRestartLocalParser),
2047
+ createOptionalParser(smaliCodePrologueEndParser),
2048
+ createOptionalParser(smaliCodeEpilogueBeginParser),
2049
+ createArrayParser(smaliCodeLabelLineParser),
853
2050
  smaliCodeOperationParser,
854
2051
  ]),
855
2052
  ([
856
- _lines,
857
- _local,
858
- _labels,
859
- operation,
2053
+ lines,
2054
+ local,
2055
+ endLocal,
2056
+ restartLocal,
2057
+ prologueEnd,
2058
+ epilogueBegin,
2059
+ labels,
2060
+ operation,
2061
+ ]) => {
2062
+ const debugDirectives: SmaliDebugDirective[] = [];
2063
+
2064
+ for (const line of lines) {
2065
+ debugDirectives.push({ type: 'line', line });
2066
+ }
2067
+
2068
+ if (local) {
2069
+ debugDirectives.push({ type: 'startLocal', local });
2070
+ }
2071
+
2072
+ if (endLocal) {
2073
+ debugDirectives.push({ type: 'endLocal', register: endLocal });
2074
+ }
2075
+
2076
+ if (restartLocal) {
2077
+ debugDirectives.push({ type: 'restartLocal', register: restartLocal });
2078
+ }
2079
+
2080
+ if (prologueEnd) {
2081
+ debugDirectives.push({ type: 'setPrologueEnd' });
2082
+ }
2083
+
2084
+ if (epilogueBegin) {
2085
+ debugDirectives.push({ type: 'setEpilogueBegin' });
2086
+ }
2087
+
2088
+ return {
2089
+ debugDirectives,
2090
+ labels,
2091
+ operation,
2092
+ };
2093
+ },
2094
+ );
2095
+
2096
+ setParserName(smaliAnnotatedCodeOperationParser, 'smaliAnnotatedCodeOperationParser');
2097
+
2098
+ type SmaliExecutableCode<DalvikBytecode> = {
2099
+ dalvikExecutableCode: DalvikExecutableCode<DalvikBytecode>;
2100
+ parameters: SmaliCodeParameter[];
2101
+ parameterAnnotations: SmaliCodeParameter[]; // TODO?: SmaliAnnotation[]
2102
+ methodAnnotations: SmaliAnnotation[];
2103
+ };
2104
+
2105
+ const smaliExecutableCodeParser: Parser<SmaliExecutableCode<DalvikBytecode>, string> = promiseCompose(
2106
+ createTupleParser([
2107
+ createOptionalParser(smaliCodeRegistersParser),
2108
+ createArrayParser(smaliAnnotationParser),
2109
+ createArrayParser(smaliCodeParameterParser),
2110
+ createOptionalParser(smaliCommentsOrNewlinesParser),
2111
+ createSeparatedArrayParser(
2112
+ smaliAnnotationParser,
2113
+ smaliCommentsOrNewlinesParser,
2114
+ ),
2115
+ createArrayParser(promiseCompose(
2116
+ createTupleParser([
2117
+ createOptionalParser(smaliCommentsOrNewlinesParser),
2118
+ createDisjunctionParser([
2119
+ smaliIndentedCommentParser,
2120
+ smaliAnnotatedCodeOperationParser,
2121
+ smaliLabeledCatchDirectiveParser,
2122
+ ]),
2123
+ ]),
2124
+ ([
2125
+ _commentsOrNewlinesParser,
2126
+ operation,
2127
+ ]) => operation,
2128
+ )),
2129
+ ]),
2130
+ ([
2131
+ registersSize,
2132
+ annotations1,
2133
+ parameters,
2134
+ _leadingCommentsOrNewlines,
2135
+ annotations2,
2136
+ instructionsAndCatchDirectives,
860
2137
  ]) => {
861
- const operation_ = {
862
- operation: operation.operation,
863
- // line,
864
- // local,
865
- // label,
866
- // parameters: (operation as any).parameters,
867
- } as any; // TODO
2138
+ const annotations = [ ...annotations1, ...annotations2 ];
2139
+ const instructions: SmaliCodeOperation[] = [];
2140
+ const annotatedOperations: SmaliAnnotatedCodeOperation[] = [];
2141
+ const catchDirectives: SmaliCatchDirective[] = [];
2142
+ const catchDirectiveLabels: Map<SmaliCatchDirective, string[]> = new Map();
2143
+
2144
+ for (const item of instructionsAndCatchDirectives) {
2145
+ if (item && typeof item === 'object') {
2146
+ if ('labels' in item && 'catchDirective' in item) {
2147
+ // This is a SmaliLabeledCatchDirective
2148
+ const labeledCatch = item as SmaliLabeledCatchDirective;
2149
+ catchDirectives.push(labeledCatch.catchDirective);
2150
+ catchDirectiveLabels.set(labeledCatch.catchDirective, labeledCatch.labels);
2151
+ } else if ('type' in item && 'startLabel' in item && 'endLabel' in item && 'handlerLabel' in item) {
2152
+ // This is a bare SmaliCatchDirective (shouldn't happen with current parser structure)
2153
+ catchDirectives.push(item as SmaliCatchDirective);
2154
+ } else if ('debugDirectives' in item && 'labels' in item && 'operation' in item) {
2155
+ // This is a SmaliAnnotatedCodeOperation
2156
+ const annotated = item as SmaliAnnotatedCodeOperation;
2157
+ annotatedOperations.push(annotated);
2158
+ const operation = annotated.operation;
2159
+ if (annotated.labels.length > 0) {
2160
+ (operation as any).labels = new Set(annotated.labels);
2161
+ }
2162
+ instructions.push(operation);
2163
+ } else if (item !== undefined) {
2164
+ // This is a SmaliCodeOperation (fallback)
2165
+ instructions.push(item as SmaliCodeOperation);
2166
+ }
2167
+ }
2168
+ }
868
2169
 
869
- for (const parameter of (operation as unknown as { parameters: unknown[]; }).parameters.flat() ?? []) {
870
- if (isDalvikExecutableMethod(parameter)) {
871
- operation_.method = parameter;
2170
+ if (
2171
+ registersSize === undefined
2172
+ && parameters !== undefined
2173
+ ) {
2174
+ registersSize = parameters.length;
2175
+ }
872
2176
 
873
- continue;
2177
+ invariant(
2178
+ registersSize !== undefined,
2179
+ 'Expected registers size to be defined',
2180
+ );
2181
+
2182
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2183
+ if (
2184
+ 'branchLabel' in operation
2185
+ && typeof operation.branchLabel === 'string'
2186
+ ) {
2187
+ for (const [ targetOperationIndex, targetOperation ] of instructions.entries()) {
2188
+ if (!isOperationWithLabels(targetOperation)) {
2189
+ continue;
2190
+ }
2191
+
2192
+ if (!targetOperation.labels.has(operation.branchLabel)) {
2193
+ continue;
2194
+ }
2195
+
2196
+ delete (operation as any).branchLabel;
2197
+ (operation as any).branchOffsetIndex = targetOperationIndex - operationIndex;
2198
+
2199
+ break;
2200
+ }
874
2201
  }
875
2202
 
876
- if (isDalvikExecutableField(parameter)) {
877
- operation_.field = parameter;
2203
+ if (
2204
+ 'branchLabels' in operation
2205
+ && typeof operation.branchLabels === 'object'
2206
+ && Array.isArray(operation.branchLabels)
2207
+ ) {
2208
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => (
2209
+ 'branchOffsetIndex' in sourceOperation
2210
+ && typeof sourceOperation.branchOffsetIndex === 'number'
2211
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex
2212
+ ));
2213
+
2214
+ invariant(
2215
+ sourceOperations.length === 1,
2216
+ 'Expected exactly one source operation to point to %s',
2217
+ JSON.stringify(operation),
2218
+ );
2219
+
2220
+ const [ sourceOperation ] = sourceOperations;
2221
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
2222
+
2223
+ (operation as any).branchOffsetIndices = operation.branchLabels.map((branchLabel: string) => {
2224
+ for (const [ targetOperationIndex, targetOperation ] of instructions.entries()) {
2225
+ if (!isOperationWithLabels(targetOperation)) {
2226
+ continue;
2227
+ }
2228
+
2229
+ if (!targetOperation.labels.has(branchLabel)) {
2230
+ continue;
2231
+ }
2232
+
2233
+ return targetOperationIndex - sourceOperationIndex;
2234
+ }
878
2235
 
879
- continue;
2236
+ invariant(false, 'Expected to find branch label %s', branchLabel);
2237
+ });
2238
+
2239
+ delete (operation as any).branchLabels;
2240
+ }
2241
+ }
2242
+
2243
+ const branchOffsetByBranchOffsetIndex = new Map<number, number>();
2244
+
2245
+ let operationOffset = 0;
2246
+
2247
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2248
+ const operationSize = getOperationFormatSize(operation);
2249
+
2250
+ branchOffsetByBranchOffsetIndex.set(
2251
+ operationIndex,
2252
+ operationOffset,
2253
+ );
2254
+
2255
+ operationOffset += operationSize;
2256
+ }
2257
+
2258
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2259
+ if (
2260
+ 'branchOffsetIndex' in operation
2261
+ && typeof operation.branchOffsetIndex === 'number'
2262
+ ) {
2263
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(operationIndex + operation.branchOffsetIndex);
2264
+ invariant(
2265
+ operationOffset !== undefined,
2266
+ 'Expected branch offset for operation index %s, but got undefined',
2267
+ operation.branchOffsetIndex,
2268
+ );
2269
+
2270
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(operationIndex);
2271
+ invariant(
2272
+ branchOffset !== undefined,
2273
+ 'Expected branch offset for operation index %s, but got undefined',
2274
+ operationIndex,
2275
+ );
2276
+
2277
+ (operation as any).branchOffset = operationOffset - branchOffset;
880
2278
  }
881
2279
 
882
- if (isSmaliRegister(parameter)) {
883
- if (!operation_.registers) {
884
- operation_.registers = [];
2280
+ if (
2281
+ 'branchOffsetIndices' in operation
2282
+ && typeof operation.branchOffsetIndices === 'object'
2283
+ && Array.isArray(operation.branchOffsetIndices)
2284
+ ) {
2285
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => (
2286
+ 'branchOffsetIndex' in sourceOperation
2287
+ && typeof sourceOperation.branchOffsetIndex === 'number'
2288
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex
2289
+ ));
2290
+
2291
+ invariant(
2292
+ sourceOperations.length === 1,
2293
+ 'Expected exactly one source operation to point to %s',
2294
+ JSON.stringify(operation),
2295
+ );
2296
+
2297
+ const [ sourceOperation ] = sourceOperations;
2298
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
2299
+
2300
+ (operation as any).branchOffsets = operation.branchOffsetIndices.map((branchOffsetIndex: number) => {
2301
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex + branchOffsetIndex);
2302
+ invariant(
2303
+ operationOffset !== undefined,
2304
+ 'Expected branch offset for operation index %s, but got undefined',
2305
+ sourceOperationIndex + branchOffsetIndex,
2306
+ );
2307
+
2308
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex);
2309
+ invariant(
2310
+ branchOffset !== undefined,
2311
+ 'Expected branch offset for operation index %s, but got undefined',
2312
+ sourceOperationIndex,
2313
+ );
2314
+
2315
+ return operationOffset - branchOffset;
2316
+ });
2317
+ }
2318
+ }
2319
+
2320
+ // Build label-to-index mapping
2321
+ // Labels attached to instructions map to that instruction's index
2322
+ // Labels before catch directives should map to the position they mark
2323
+ const labelToIndexMap = new Map<string, number>();
2324
+
2325
+ // First, map labels from instructions
2326
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2327
+ if (
2328
+ operation
2329
+ && typeof operation === 'object'
2330
+ && 'labels' in operation
2331
+ && operation.labels instanceof Set
2332
+ ) {
2333
+ for (const label of operation.labels) {
2334
+ labelToIndexMap.set(label, operationIndex);
885
2335
  }
2336
+ }
2337
+ }
886
2338
 
887
- operation_.registers.push(parameter);
2339
+ // Now handle labels from catch directives
2340
+ // We need to figure out where each catch directive appears in the original sequence
2341
+ let instructionArrayIndex = 0;
2342
+ for (let i = 0; i < instructionsAndCatchDirectives.length; i++) {
2343
+ const item = instructionsAndCatchDirectives[i];
2344
+ if (item && typeof item === 'object' && 'labels' in item && 'catchDirective' in item) {
2345
+ // This is a catch directive with labels
2346
+ // These labels should map to the current instruction index
2347
+ // (which is where the next instruction would be)
2348
+ const labeledCatch = item as SmaliLabeledCatchDirective;
2349
+ for (const label of labeledCatch.labels) {
2350
+ labelToIndexMap.set(label, instructionArrayIndex);
2351
+ }
2352
+ } else if (item !== undefined && !(item && typeof item === 'object' && 'type' in item && 'startLabel' in item)) {
2353
+ // This is an instruction, increment the counter
2354
+ instructionArrayIndex++;
2355
+ }
2356
+ }
888
2357
 
889
- continue;
2358
+ // Build tries array from catch directives
2359
+ const triesByRange = new Map<string, {
2360
+ startAddress: number;
2361
+ instructionCount: number;
2362
+ handlers: Array<{ type: string; address: number }>;
2363
+ catchAllAddress: number | undefined;
2364
+ }>();
2365
+
2366
+ for (const catchDirective of catchDirectives) {
2367
+ // Find the start and end instruction indices
2368
+ const startIndex = labelToIndexMap.get(catchDirective.startLabel);
2369
+ const endIndex = labelToIndexMap.get(catchDirective.endLabel);
2370
+ const handlerIndex = labelToIndexMap.get(catchDirective.handlerLabel);
2371
+
2372
+ invariant(startIndex !== undefined, 'Expected to find start label %s', catchDirective.startLabel);
2373
+ invariant(endIndex !== undefined, 'Expected to find end label %s', catchDirective.endLabel);
2374
+ invariant(handlerIndex !== undefined, 'Expected to find handler label %s', catchDirective.handlerLabel);
2375
+
2376
+ const startAddress = branchOffsetByBranchOffsetIndex.get(startIndex);
2377
+ const endAddress = branchOffsetByBranchOffsetIndex.get(endIndex);
2378
+ const handlerAddress = branchOffsetByBranchOffsetIndex.get(handlerIndex);
2379
+
2380
+ invariant(startAddress !== undefined, 'Expected start address for index %s', startIndex);
2381
+ invariant(endAddress !== undefined, 'Expected end address for index %s', endIndex);
2382
+ invariant(handlerAddress !== undefined, 'Expected handler address for index %s', handlerIndex);
2383
+
2384
+ const instructionCount = endAddress - startAddress;
2385
+ const rangeKey = `${startAddress}-${instructionCount}`;
2386
+
2387
+ let tryEntry = triesByRange.get(rangeKey);
2388
+ if (!tryEntry) {
2389
+ tryEntry = {
2390
+ startAddress,
2391
+ instructionCount,
2392
+ handlers: [],
2393
+ catchAllAddress: undefined,
2394
+ };
2395
+ triesByRange.set(rangeKey, tryEntry);
890
2396
  }
891
2397
 
892
- if (typeof parameter === 'string') {
893
- if (operationsWithTypeArgument.has(operation_.operation)) {
894
- operation_.type = parameter;
2398
+ if (catchDirective.type === undefined) {
2399
+ // .catchall
2400
+ tryEntry.catchAllAddress = handlerAddress;
2401
+ } else {
2402
+ // .catch Type
2403
+ tryEntry.handlers.push({
2404
+ type: catchDirective.type,
2405
+ address: handlerAddress,
2406
+ });
2407
+ }
2408
+ }
895
2409
 
896
- continue;
2410
+ const tries = Array.from(triesByRange.values()).map(tryEntry => ({
2411
+ startAddress: tryEntry.startAddress,
2412
+ instructionCount: tryEntry.instructionCount,
2413
+ handler: {
2414
+ handlers: tryEntry.handlers,
2415
+ catchAllAddress: tryEntry.catchAllAddress,
2416
+ },
2417
+ }));
2418
+
2419
+ for (const operation of instructions) {
2420
+ delete (operation as any).labels;
2421
+ delete (operation as any).branchOffsetIndex;
2422
+ delete (operation as any).branchOffsetIndices;
2423
+ }
2424
+
2425
+ // Build debug info from debug directives
2426
+ type DebugByteCodeValue = DalvikExecutableDebugInfo['bytecode'][number];
2427
+ let debugInfo: DalvikExecutableDebugInfo | undefined;
2428
+ const debugBytecode: DebugByteCodeValue[] = [];
2429
+ let currentLine: number | undefined;
2430
+ let lineStart: number | undefined;
2431
+
2432
+ // Helper to convert SmaliRegister to register number
2433
+ const getRegisterNum = (register: SmaliRegister): number => {
2434
+ // In Smali, 'v' registers start from 0, 'p' registers are parameters
2435
+ // For now, we'll just use the index. The actual conversion may need
2436
+ // to account for registersSize and parameter mapping.
2437
+ if (register.prefix === 'v') {
2438
+ return register.index;
2439
+ } else {
2440
+ // 'p' registers: need to map to actual register numbers
2441
+ // p0 starts at (registersSize - insSize)
2442
+ // For now, just use the index as we don't have accurate insSize
2443
+ return register.index;
2444
+ }
2445
+ };
2446
+
2447
+ // Helper to emit accumulated line/address changes as special opcode or explicit opcodes
2448
+ const emitPendingChanges = (lineDiff: number, addressDiff: number) => {
2449
+ // Special opcode formula: opcode = 0x0A + (line_diff + 4) + 15 * addr_diff
2450
+ // Valid when: line_diff ∈ [-4, 10] and addr_diff ∈ [0, 17] and result ≤ 0xFF
2451
+ if (lineDiff >= -4 && lineDiff <= 10 && addressDiff >= 0) {
2452
+ const adjusted = (lineDiff + 4) + 15 * addressDiff;
2453
+ const opcode = 0x0A + adjusted;
2454
+ if (opcode <= 0xFF) {
2455
+ debugBytecode.push({
2456
+ type: 'special',
2457
+ value: opcode,
2458
+ });
2459
+ return;
897
2460
  }
2461
+ }
898
2462
 
899
- operation_.string = parameter;
2463
+ // Fall back to explicit opcodes
2464
+ if (addressDiff > 0) {
2465
+ debugBytecode.push({
2466
+ type: 'advancePc',
2467
+ addressDiff,
2468
+ });
2469
+ }
2470
+ if (lineDiff !== 0) {
2471
+ debugBytecode.push({
2472
+ type: 'advanceLine',
2473
+ lineDiff,
2474
+ });
2475
+ }
2476
+ };
900
2477
 
901
- continue;
2478
+ let accumulatedAddressDiff = 0;
2479
+ let accumulatedLineDiff = 0;
2480
+ let lastAddress = 0;
2481
+ let hasEmittedFirstDebugEvent = false;
2482
+
2483
+ for (let i = 0; i < annotatedOperations.length; i++) {
2484
+ const annotated = annotatedOperations[i];
2485
+ const operationAddress = branchOffsetByBranchOffsetIndex.get(i) ?? 0;
2486
+
2487
+ // Accumulate address difference (only after we've started tracking)
2488
+ if (i > 0 && hasEmittedFirstDebugEvent) {
2489
+ accumulatedAddressDiff += operationAddress - lastAddress;
2490
+ }
2491
+ lastAddress = operationAddress;
2492
+
2493
+ // Process line directives first to update accumulatedLineDiff
2494
+ for (const directive of annotated.debugDirectives) {
2495
+ if (directive.type === 'line') {
2496
+ if (lineStart === undefined) {
2497
+ lineStart = directive.line;
2498
+ currentLine = directive.line;
2499
+ } else if (currentLine !== undefined) {
2500
+ accumulatedLineDiff += directive.line - currentLine;
2501
+ currentLine = directive.line;
2502
+ }
2503
+ }
902
2504
  }
903
2505
 
904
- if (typeof parameter === 'number' || typeof parameter === 'bigint') {
905
- operation_.value = parameter;
2506
+ // Emit accumulated changes before any other debug events
2507
+ const hasOtherDebugEvents = annotated.debugDirectives.some(d =>
2508
+ d.type !== 'line'
2509
+ );
906
2510
 
907
- continue;
2511
+ if (hasOtherDebugEvents && (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0 || !hasEmittedFirstDebugEvent)) {
2512
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
2513
+ accumulatedLineDiff = 0;
2514
+ accumulatedAddressDiff = 0;
2515
+ hasEmittedFirstDebugEvent = true;
908
2516
  }
909
2517
 
910
- invariant(false, 'TODO: parameter: %s, operation: %s', JSON.stringify(parameter), JSON.stringify(operation));
2518
+ // Emit other debug directives
2519
+ for (const directive of annotated.debugDirectives) {
2520
+ if (directive.type === 'startLocal') {
2521
+ debugBytecode.push({
2522
+ type: 'startLocal',
2523
+ registerNum: getRegisterNum(directive.local.register),
2524
+ name: directive.local.name,
2525
+ type_: directive.local.type,
2526
+ });
2527
+ } else if (directive.type === 'endLocal') {
2528
+ debugBytecode.push({
2529
+ type: 'endLocal',
2530
+ registerNum: getRegisterNum(directive.register),
2531
+ });
2532
+ } else if (directive.type === 'restartLocal') {
2533
+ debugBytecode.push({
2534
+ type: 'restartLocal',
2535
+ registerNum: getRegisterNum(directive.register),
2536
+ });
2537
+ } else if (directive.type === 'setPrologueEnd') {
2538
+ debugBytecode.push({
2539
+ type: 'setPrologueEnd',
2540
+ });
2541
+ } else if (directive.type === 'setEpilogueBegin') {
2542
+ debugBytecode.push({
2543
+ type: 'setEpilogueBegin',
2544
+ });
2545
+ }
2546
+ }
911
2547
  }
912
2548
 
913
- if (operation_.registers) {
914
- operation_.registers = operation_.registers.reverse();
2549
+ // Emit any remaining accumulated changes at the end
2550
+ if (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0) {
2551
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
915
2552
  }
916
2553
 
917
- return operation_;
918
- },
919
- );
2554
+ if (lineStart !== undefined) {
2555
+ // Extract parameter names from .param directives
2556
+ const parameterNames: Array<undefined | string> = [];
2557
+ // TODO: Extract from parameters array if needed
2558
+
2559
+ // If we have a lineStart but no bytecode, we need to emit a special opcode
2560
+ // to establish the initial state. The special value 0x0E (14) means:
2561
+ // adjusted = 14 - 10 = 4, line_diff = 4 % 15 - 4 = 0, pc_diff = 4 / 15 = 0
2562
+ if (debugBytecode.length === 0) {
2563
+ debugBytecode.push({
2564
+ type: 'special',
2565
+ value: 0x0A + 4, // 14 in decimal
2566
+ });
2567
+ }
920
2568
 
921
- setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
2569
+ debugInfo = {
2570
+ lineStart,
2571
+ parameterNames,
2572
+ bytecode: debugBytecode,
2573
+ };
2574
+ }
922
2575
 
923
- const smaliExecutableCodeParser: Parser<DalvikExecutableCode<DalvikBytecode>, string> = promiseCompose(
924
- createTupleParser([
925
- smaliCodeRegistersParser,
926
- createArrayParser(
927
- smaliAnnotationParser,
928
- ),
929
- createOptionalParser(smaliCodeParameterParser),
930
- createArrayParser(
931
- promiseCompose(
932
- createTupleParser([
933
- createOptionalParser(smaliCommentsOrNewlinesParser),
934
- smaliAnnotatedCodeOperationParser,
935
- ]),
936
- ([
937
- _commentsOrNewlines,
938
- operation,
939
- ]) => operation,
940
- ),
941
- ),
942
- ]),
943
- ([
944
- registersSize,
945
- _annotations,
946
- _parameters,
947
- instructions,
948
- ]) => {
949
2576
  return {
950
- registersSize,
951
- insSize: -1, // TODO
952
- outsSize: -1, // TODO
953
- debugInfo: undefined, // TODO
954
- instructions,
955
- tries: [], // TODO
956
- // _annotations,
2577
+ dalvikExecutableCode: {
2578
+ registersSize,
2579
+ insSize: -1, // TODO
2580
+ outsSize: -1, // TODO
2581
+ debugInfo,
2582
+ instructions: instructions as any, // TODO
2583
+ tries,
2584
+ // _annotations,
2585
+ },
2586
+ parameters,
2587
+ parameterAnnotations: parameters.filter(parameter => parameter.annotation !== undefined),
2588
+ methodAnnotations: annotations,
957
2589
  };
958
2590
  },
959
2591
  );
960
2592
 
961
2593
  setParserName(smaliExecutableCodeParser, 'smaliExecutableCodeParser');
962
2594
 
963
- const smaliMethodParser: Parser<DalvikExecutableMethodWithAccess<DalvikBytecode>, string> = promiseCompose(
2595
+ // TODO: ???
2596
+ const sortRegistersOperations = new Set<DalvikBytecodeOperation['operation']>([
2597
+ 'if-eq',
2598
+ 'if-ne',
2599
+ ]);
2600
+
2601
+ // All 12x format operations need register reversal because:
2602
+ // - Bytecode format is B|A (B in high nibble, A in low nibble)
2603
+ // - nibblesParser returns [B, A]
2604
+ // - Smali syntax is "op vA, vB" (destination first)
2605
+ // - So we need to reverse to get [A, B]
2606
+ const reverseRegistersOperations = new Set<DalvikBytecodeOperation['operation']>([
2607
+ // Move operations
2608
+ 'move',
2609
+ 'move-wide',
2610
+ 'move-object',
2611
+
2612
+ // Unary operations (negation, bitwise not)
2613
+ 'neg-int',
2614
+ 'not-int',
2615
+ 'neg-long',
2616
+ 'not-long',
2617
+ 'neg-float',
2618
+ 'neg-double',
2619
+
2620
+ // Type conversion operations
2621
+ 'int-to-long',
2622
+ 'int-to-float',
2623
+ 'int-to-double',
2624
+ 'long-to-int',
2625
+ 'long-to-float',
2626
+ 'long-to-double',
2627
+ 'float-to-int',
2628
+ 'float-to-long',
2629
+ 'float-to-double',
2630
+ 'double-to-int',
2631
+ 'double-to-long',
2632
+ 'double-to-float',
2633
+ 'int-to-byte',
2634
+ 'int-to-char',
2635
+ 'int-to-short',
2636
+
2637
+ // Binary operations with /2addr suffix
2638
+ 'add-int/2addr',
2639
+ 'sub-int/2addr',
2640
+ 'mul-int/2addr',
2641
+ 'div-int/2addr',
2642
+ 'rem-int/2addr',
2643
+ 'and-int/2addr',
2644
+ 'or-int/2addr',
2645
+ 'xor-int/2addr',
2646
+ 'shl-int/2addr',
2647
+ 'shr-int/2addr',
2648
+ 'ushr-int/2addr',
2649
+
2650
+ 'add-long/2addr',
2651
+ 'sub-long/2addr',
2652
+ 'mul-long/2addr',
2653
+ 'div-long/2addr',
2654
+ 'rem-long/2addr',
2655
+ 'and-long/2addr',
2656
+ 'or-long/2addr',
2657
+ 'xor-long/2addr',
2658
+ 'shl-long/2addr',
2659
+ 'shr-long/2addr',
2660
+ 'ushr-long/2addr',
2661
+
2662
+ 'add-float/2addr',
2663
+ 'sub-float/2addr',
2664
+ 'mul-float/2addr',
2665
+ 'div-float/2addr',
2666
+ 'rem-float/2addr',
2667
+
2668
+ 'add-double/2addr',
2669
+ 'sub-double/2addr',
2670
+ 'mul-double/2addr',
2671
+ 'div-double/2addr',
2672
+ 'rem-double/2addr',
2673
+ ]);
2674
+
2675
+ function normalizeOperation(operation: DalvikBytecodeOperation): DalvikBytecodeOperation {
2676
+ if (
2677
+ sortRegistersOperations.has(operation.operation)
2678
+ && 'registers' in operation
2679
+ ) {
2680
+ operation.registers.sort((a, b) => a - b);
2681
+ }
2682
+
2683
+ if (
2684
+ reverseRegistersOperations.has(operation.operation)
2685
+ && 'registers' in operation
2686
+ ) {
2687
+ operation.registers.reverse();
2688
+ }
2689
+
2690
+ return operation;
2691
+ }
2692
+
2693
+ type SmaliMethod<DalvikBytecode> = {
2694
+ dalvikExecutableMethodWithAccess: DalvikExecutableMethodWithAccess<DalvikBytecode>;
2695
+ parameterAnnotations: SmaliCodeParameter[];
2696
+ methodAnnotations: SmaliAnnotation[];
2697
+ };
2698
+
2699
+ export const smaliMethodParser: Parser<SmaliMethod<DalvikBytecode>, string> = promiseCompose(
964
2700
  createTupleParser([
965
2701
  createExactSequenceParser('.method '),
966
- smaliAccessFlagsParser,
967
- createExactSequenceParser(' '),
2702
+ createOptionalParser(promiseCompose(
2703
+ createTupleParser([
2704
+ smaliAccessFlagsParser,
2705
+ smaliSingleWhitespaceParser,
2706
+ ]),
2707
+ ([accessFlags]) => accessFlags,
2708
+ )),
968
2709
  smaliMemberNameParser,
969
2710
  smaliMethodPrototypeParser,
970
- createExactSequenceParser('\n'),
2711
+ smaliLineEndPraser,
971
2712
  smaliExecutableCodeParser,
972
2713
  createArrayParser(smaliCodeLineParser),
973
2714
  createExactSequenceParser('.end method\n'),
974
2715
  ]),
975
2716
  ([
976
2717
  _method,
977
- accessFlags,
978
- _space,
2718
+ accessFlagsOrUndefined,
979
2719
  name,
980
2720
  prototype,
981
2721
  _newline,
982
- code,
2722
+ {
2723
+ dalvikExecutableCode,
2724
+ parameters,
2725
+ parameterAnnotations,
2726
+ methodAnnotations,
2727
+ },
983
2728
  _lines,
984
2729
  _endMethod,
985
2730
  ]) => {
986
- let insSize = 1;
2731
+ const accessFlags = accessFlagsOrUndefined ?? dalvikExecutableAccessFlagsDefault();
2732
+ let code: DalvikExecutableCode<DalvikBytecode> | undefined = dalvikExecutableCode;
987
2733
 
988
- for (const _parameter of prototype.parameters) {
989
- insSize += 1; // TODO: two words for wide types
2734
+ if (accessFlags.native && code.instructions.length === 0) {
2735
+ code = undefined;
990
2736
  }
991
2737
 
992
- code.insSize = insSize;
2738
+ if (code) {
2739
+ const insSize = (accessFlags.static ? 0 : 1) + shortyGetInsSize(prototype.shorty);
993
2740
 
994
- for (const operation of code.instructions) {
995
- const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation) as unknown[] as SmaliRegister[]; // TODO
2741
+ code.insSize = insSize;
996
2742
 
997
- if (!smaliRegisters.length) {
998
- continue;
999
- }
2743
+ for (const operation of code.instructions) {
2744
+ const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation) as unknown[] as SmaliRegister[]; // TODO
1000
2745
 
1001
- const registers: number[] = smaliRegisters.map(({ prefix, index }) => {
1002
- if (prefix === 'v') {
1003
- return index;
2746
+ if (smaliRegisters.length === 0) {
2747
+ continue;
1004
2748
  }
1005
2749
 
1006
- if (prefix === 'p') {
1007
- return code.registersSize - insSize + index;
1008
- }
2750
+ const registers: number[] = smaliRegisters.map(({ prefix, index }) => {
2751
+ if (prefix === 'v') {
2752
+ return index;
2753
+ }
1009
2754
 
1010
- invariant(false, 'Expected prefix to be v or p');
1011
- });
2755
+ if (prefix === 'p') {
2756
+ return code.registersSize - insSize + index;
2757
+ }
1012
2758
 
1013
- (operation as any).registers = registers; // TODO
1014
- }
2759
+ invariant(false, 'Expected prefix to be v or p');
2760
+ });
2761
+
2762
+ (operation as any).registers = registers; // TODO
2763
+ }
1015
2764
 
1016
- let outsSize = 0;
2765
+ let outsSize = 0;
1017
2766
 
1018
- for (const operation of code.instructions) {
1019
- const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
2767
+ for (const operation of code.instructions) {
2768
+ if (!operation.operation.startsWith('invoke-')) {
2769
+ continue;
2770
+ }
1020
2771
 
1021
- outsSize = Math.max(outsSize, registers.length); // TODO: two words for wide types
1022
- }
2772
+ const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
2773
+
2774
+ outsSize = Math.max(outsSize, registers.length); // TODO?: two words for wide types?
2775
+ }
2776
+
2777
+ code.outsSize = outsSize;
2778
+
2779
+ // Populate debug info parameter names
2780
+ // The parameter count is insSize minus 1 for non-static methods (to exclude 'this')
2781
+ if (code.debugInfo) {
2782
+ const paramCount = accessFlags.static ? insSize : insSize - 1;
2783
+ const paramNames: Array<undefined | string> = Array(paramCount).fill(undefined);
2784
+
2785
+ // Map parameter names from .param directives
2786
+ // For non-static methods: p0 is 'this', p1 is first param (index 0)
2787
+ // For static methods: p0 is first param (index 0)
2788
+ for (const param of parameters) {
2789
+ if (param.register.prefix === 'p' && param.name) {
2790
+ const pRegister = param.register.index;
2791
+ // Adjust for 'this' parameter in non-static methods
2792
+ const paramIndex = accessFlags.static ? pRegister : pRegister - 1;
2793
+ if (paramIndex >= 0 && paramIndex < paramCount) {
2794
+ paramNames[paramIndex] = param.name;
2795
+ }
2796
+ }
2797
+ }
1023
2798
 
1024
- code.outsSize = outsSize;
2799
+ code.debugInfo.parameterNames = paramNames;
2800
+ }
2801
+ }
1025
2802
 
1026
2803
  return {
1027
- accessFlags,
1028
- method: {
1029
- class: 'FILLED_LATER',
1030
- prototype,
1031
- name,
2804
+ dalvikExecutableMethodWithAccess: {
2805
+ accessFlags,
2806
+ method: {
2807
+ class: 'FILLED_LATER',
2808
+ prototype,
2809
+ name,
2810
+ },
2811
+ code: code?.instructions.length
2812
+ ? {
2813
+ ...code,
2814
+ instructions: code.instructions.map(normalizeOperation),
2815
+ }
2816
+ : undefined,
1032
2817
  },
1033
- code,
2818
+ parameterAnnotations,
2819
+ methodAnnotations,
1034
2820
  };
1035
2821
  },
1036
2822
  );
1037
2823
 
1038
2824
  setParserName(smaliMethodParser, 'smaliMethodParser');
1039
2825
 
1040
- type SmaliMethods = Pick<DalvikExecutableClassData<DalvikBytecode>, 'directMethods' | 'virtualMethods'>;
2826
+ type SmaliMethods = Pick<DalvikExecutableClassData<DalvikBytecode>, 'directMethods' | 'virtualMethods'> & {
2827
+ parameterAnnotations: DalvikExecutableClassParameterAnnotation[];
2828
+ methodAnnotations: DalvikExecutableClassMethodAnnotation[];
2829
+ };
1041
2830
 
1042
2831
  const smaliMethodsParser: Parser<SmaliMethods, string> = promiseCompose(
1043
- createArrayParser<string[] | DalvikExecutableMethodWithAccess<DalvikBytecode>, string>(
1044
- createDisjunctionParser<string[] | DalvikExecutableMethodWithAccess<DalvikBytecode>, string, string>([
1045
- smaliMethodParser,
1046
- smaliCommentsOrNewlinesParser,
1047
- ]),
1048
- ),
1049
- (methodsAndComments) => {
2832
+ createArrayParser<string[] | SmaliMethod<DalvikBytecode>, string>(createDisjunctionParser<string[] | SmaliMethod<DalvikBytecode>, string, string>([
2833
+ smaliMethodParser,
2834
+ smaliCommentsOrNewlinesParser,
2835
+ ])),
2836
+ methodsAndComments => {
1050
2837
  let type: 'directMethod' | 'virtualMethod' = 'directMethod';
1051
2838
 
1052
- const directMethods: DalvikExecutableMethodWithAccess<DalvikBytecode>[] = [];
1053
- const virtualMethods: DalvikExecutableMethodWithAccess<DalvikBytecode>[] = [];
2839
+ const directMethods: Array<DalvikExecutableMethodWithAccess<DalvikBytecode>> = [];
2840
+ const virtualMethods: Array<DalvikExecutableMethodWithAccess<DalvikBytecode>> = [];
2841
+
2842
+ const parameterAnnotations: DalvikExecutableClassParameterAnnotation[] = [];
2843
+ const methodAnnotations: DalvikExecutableClassMethodAnnotation[] = [];
2844
+
2845
+ function pushParameterAnnotation(annotation: DalvikExecutableClassParameterAnnotation) {
2846
+ if (annotation.annotations.length === 0) {
2847
+ return;
2848
+ }
2849
+
2850
+ const existingMethod = parameterAnnotations.find(parameterAnnotation => dalvikExecutableMethodEquals(
2851
+ parameterAnnotation.method,
2852
+ annotation.method,
2853
+ ));
2854
+
2855
+ if (existingMethod) {
2856
+ for (const [ index, parameterAnnotations_ ] of annotation.annotations.entries()) {
2857
+ const existingParameterAnnotations = existingMethod.annotations.at(index);
2858
+
2859
+ if (existingParameterAnnotations) {
2860
+ existingParameterAnnotations.push(...parameterAnnotations_);
2861
+ } else {
2862
+ existingMethod.annotations[index] = parameterAnnotations_;
2863
+ }
2864
+ }
2865
+
2866
+ return;
2867
+ }
2868
+
2869
+ parameterAnnotations.push(annotation);
2870
+ }
1054
2871
 
1055
2872
  for (const methodOrComment of methodsAndComments) {
1056
2873
  if (Array.isArray(methodOrComment)) {
@@ -1069,16 +2886,62 @@ const smaliMethodsParser: Parser<SmaliMethods, string> = promiseCompose(
1069
2886
 
1070
2887
  invariant(typeof methodOrComment === 'object', 'Expected method or comment');
1071
2888
 
1072
- const method = methodOrComment as DalvikExecutableMethodWithAccess<DalvikBytecode>;
2889
+ const method = methodOrComment;
2890
+
2891
+ if (method.methodAnnotations.length > 0) {
2892
+ methodAnnotations.push({
2893
+ method: method.dalvikExecutableMethodWithAccess.method,
2894
+ annotations: method.methodAnnotations.map(annotation => ({
2895
+ type: annotation.type,
2896
+ visibility: annotation.visibility,
2897
+ elements: annotation.elements.map(element => ({
2898
+ name: element.name,
2899
+ value: convertToTaggedEncodedValue(element.value),
2900
+ })),
2901
+ })),
2902
+ });
2903
+ }
2904
+
2905
+ // Create an annotations array for all parameters, not just those with annotations
2906
+ // In smali, instance methods have p0 as 'this', p1 as first param, etc.
2907
+ // But DEX parameter annotations only include the actual parameters (not 'this')
2908
+ const isStatic = method.dalvikExecutableMethodWithAccess.accessFlags.static;
2909
+ const smaliRegisterOffset = isStatic ? 0 : 1; // P0 is 'this' for instance methods
2910
+
2911
+ const allParameterAnnotations = method.dalvikExecutableMethodWithAccess.method.prototype.parameters.map((_, parameterIndex) => {
2912
+ const smaliRegisterIndex = parameterIndex + smaliRegisterOffset;
2913
+ const parameterAnnotation = method.parameterAnnotations.find(pa => pa.register.prefix === 'p' && pa.register.index === smaliRegisterIndex);
2914
+
2915
+ if (parameterAnnotation?.annotation) {
2916
+ return [ {
2917
+ type: parameterAnnotation.annotation.type,
2918
+ visibility: parameterAnnotation.annotation.visibility,
2919
+ elements: parameterAnnotation.annotation.elements.map(element => ({
2920
+ name: element.name,
2921
+ value: convertToTaggedEncodedValue(element.value),
2922
+ })),
2923
+ } ];
2924
+ }
2925
+
2926
+ return [];
2927
+ });
2928
+
2929
+ // Only push parameter annotations if there are some actual annotations
2930
+ if (allParameterAnnotations.some(annotations => annotations.length > 0)) {
2931
+ pushParameterAnnotation({
2932
+ method: method.dalvikExecutableMethodWithAccess.method,
2933
+ annotations: allParameterAnnotations,
2934
+ });
2935
+ }
1073
2936
 
1074
2937
  if (type === 'directMethod') {
1075
- directMethods.push(method);
2938
+ directMethods.push(method.dalvikExecutableMethodWithAccess);
1076
2939
 
1077
2940
  continue;
1078
2941
  }
1079
2942
 
1080
2943
  if (type === 'virtualMethod') {
1081
- virtualMethods.push(method);
2944
+ virtualMethods.push(method.dalvikExecutableMethodWithAccess);
1082
2945
 
1083
2946
  continue;
1084
2947
  }
@@ -1086,11 +2949,22 @@ const smaliMethodsParser: Parser<SmaliMethods, string> = promiseCompose(
1086
2949
  invariant(false, 'Expected method type');
1087
2950
  }
1088
2951
 
2952
+ // Sort parameter annotations by method index in the combined method list
2953
+ // to match the order in the DEX file's annotations directory
2954
+ const allMethods = [...directMethods, ...virtualMethods];
2955
+ parameterAnnotations.sort((a, b) => {
2956
+ const indexA = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, a.method));
2957
+ const indexB = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, b.method));
2958
+ return indexA - indexB;
2959
+ });
2960
+
1089
2961
  return {
1090
2962
  directMethods,
1091
2963
  virtualMethods,
2964
+ parameterAnnotations,
2965
+ methodAnnotations,
1092
2966
  };
1093
- }
2967
+ },
1094
2968
  );
1095
2969
 
1096
2970
  setParserName(smaliMethodsParser, 'smaliMethodsParser');
@@ -1099,11 +2973,40 @@ export const smaliParser: Parser<DalvikExecutableClassDefinition<DalvikBytecode>
1099
2973
  createTupleParser([
1100
2974
  smaliClassDeclarationParser,
1101
2975
  smaliSuperDeclarationParser,
1102
- smaliSourceDeclarationParser,
1103
- smaliCommentsOrNewlinesParser,
1104
- createArrayParser(smaliInterfaceDeclarationParser),
1105
- smaliCommentsOrNewlinesParser,
1106
- createOptionalParser(smaliFieldsParser),
2976
+ createOptionalParser(smaliSourceDeclarationParser),
2977
+ createOptionalParser(promiseCompose(
2978
+ createTupleParser([
2979
+ smaliCommentsOrNewlinesParser,
2980
+ createNonEmptyArrayParser(smaliInterfaceDeclarationParser),
2981
+ ]),
2982
+ ([
2983
+ _commentsOrNewlines,
2984
+ interfaces,
2985
+ ]) => interfaces,
2986
+ )),
2987
+ createOptionalParser(promiseCompose(
2988
+ createTupleParser([
2989
+ smaliCommentsOrNewlinesParser,
2990
+ createSeparatedNonEmptyArrayParser<SmaliAnnotation, string>(
2991
+ smaliAnnotationParser,
2992
+ smaliCommentsOrNewlinesParser,
2993
+ ),
2994
+ ]),
2995
+ ([
2996
+ _commentsOrNewlines,
2997
+ classAnnotations,
2998
+ ]) => classAnnotations,
2999
+ )),
3000
+ createOptionalParser(promiseCompose(
3001
+ createTupleParser([
3002
+ smaliCommentsOrNewlinesParser,
3003
+ smaliFieldsParser,
3004
+ ]),
3005
+ ([
3006
+ _commentsOrNewlines,
3007
+ fields,
3008
+ ]) => fields,
3009
+ )),
1107
3010
  smaliMethodsParser,
1108
3011
  ]),
1109
3012
  ([
@@ -1114,53 +3017,332 @@ export const smaliParser: Parser<DalvikExecutableClassDefinition<DalvikBytecode>
1114
3017
  {
1115
3018
  superclass,
1116
3019
  },
1117
- {
1118
- sourceFile,
1119
- },
1120
- _commentsOrNewlines0,
3020
+ sourceFileObject,
1121
3021
  interfaces,
1122
- _commentsOrNewlines1,
1123
- fields,
3022
+ classAnnotations,
3023
+ smaliFields,
1124
3024
  methods,
1125
- ]) => ({
1126
- accessFlags,
1127
- class: class_,
1128
- superclass,
1129
- sourceFile,
1130
- annotations: undefined, // TODO
1131
- classData: {
1132
- directMethods: methods.directMethods.map((method) => ({
1133
- ...method,
1134
- method: {
1135
- ...method.method,
1136
- class: class_,
1137
- },
1138
- })),
1139
- virtualMethods: methods.virtualMethods.map((method) => ({
1140
- ...method,
1141
- method: {
1142
- ...method.method,
1143
- class: class_,
1144
- },
1145
- })),
1146
- staticFields: (fields?.staticFields ?? []).map((field) => ({
1147
- ...field,
1148
- field: {
1149
- ...field.field,
1150
- class: class_,
1151
- },
1152
- })),
1153
- instanceFields: (fields?.instanceFields ?? []).map((field) => ({
1154
- ...field,
1155
- field: {
1156
- ...field.field,
1157
- class: class_,
1158
- },
3025
+ ]) => {
3026
+ const sourceFile = sourceFileObject?.sourceFile;
3027
+ // Create staticValues array matching DEX format:
3028
+ // - Find the last static field with a non-default initializer
3029
+ // - Create array up to that index with values/nulls
3030
+ // - Fields after the last initializer are not included
3031
+ // - Default values (false, null) are not considered initializers
3032
+ const staticFieldsList = smaliFields?.staticFields ?? [];
3033
+ let lastIndexWithInitializer = -1;
3034
+ for (let i = staticFieldsList.length - 1; i >= 0; i--) {
3035
+ const initValue = staticFieldsList[i].initialValue;
3036
+ // Only consider non-default values as initializers
3037
+ // Numbers, bigints, strings, and true are non-default
3038
+ // false and null are default values
3039
+ if (initValue !== undefined && typeof initValue === 'number') {
3040
+ lastIndexWithInitializer = i;
3041
+ break;
3042
+ }
3043
+
3044
+ if (initValue !== undefined && typeof initValue === 'bigint') {
3045
+ lastIndexWithInitializer = i;
3046
+ break;
3047
+ }
3048
+
3049
+ if (initValue !== undefined && typeof initValue === 'string') {
3050
+ lastIndexWithInitializer = i;
3051
+ break;
3052
+ }
3053
+
3054
+ if (initValue === true) {
3055
+ lastIndexWithInitializer = i;
3056
+ break;
3057
+ }
3058
+ }
3059
+
3060
+ const staticValues = lastIndexWithInitializer === -1
3061
+ ? []
3062
+ : staticFieldsList
3063
+ .slice(0, lastIndexWithInitializer + 1)
3064
+ .map(smaliField => {
3065
+ const fieldType = smaliField.field.field.type;
3066
+
3067
+ if (smaliField.initialValue === undefined) {
3068
+ // For integer types without initializer, DEX stores 0
3069
+ if (fieldType === 'I') {
3070
+ return { type: 'int' as const, value: 0 };
3071
+ }
3072
+ if (fieldType === 'B') {
3073
+ return { type: 'byte' as const, value: 0 };
3074
+ }
3075
+ if (fieldType === 'S') {
3076
+ return { type: 'short' as const, value: 0 };
3077
+ }
3078
+ if (fieldType === 'C') {
3079
+ return { type: 'char' as const, value: 0 };
3080
+ }
3081
+ // For long types without initializer, DEX stores 0n
3082
+ if (fieldType === 'J') {
3083
+ return { type: 'long' as const, value: 0n };
3084
+ }
3085
+ // For float/double types without initializer, DEX stores 0
3086
+ if (fieldType === 'F') {
3087
+ return { type: 'float' as const, value: 0 };
3088
+ }
3089
+ if (fieldType === 'D') {
3090
+ return { type: 'double' as const, value: 0 };
3091
+ }
3092
+ // For boolean types without initializer, DEX stores false
3093
+ if (fieldType === 'Z') {
3094
+ return { type: 'boolean' as const, value: false };
3095
+ }
3096
+ // For other types (reference types, etc.), return null
3097
+ return { type: 'null' as const, value: null };
3098
+ }
3099
+
3100
+ // Numeric values are stored in static values array
3101
+ if (typeof smaliField.initialValue === 'number') {
3102
+ // Convert to BigInt for long (J) types
3103
+ if (fieldType === 'J') {
3104
+ return { type: 'long' as const, value: BigInt(smaliField.initialValue) };
3105
+ }
3106
+ if (fieldType === 'B') {
3107
+ return { type: 'byte' as const, value: smaliField.initialValue };
3108
+ }
3109
+ if (fieldType === 'S') {
3110
+ return { type: 'short' as const, value: smaliField.initialValue };
3111
+ }
3112
+ if (fieldType === 'C') {
3113
+ return { type: 'char' as const, value: smaliField.initialValue };
3114
+ }
3115
+ if (fieldType === 'F') {
3116
+ return { type: 'float' as const, value: smaliField.initialValue };
3117
+ }
3118
+ if (fieldType === 'D') {
3119
+ return { type: 'double' as const, value: smaliField.initialValue };
3120
+ }
3121
+ // Default to int for other numeric types
3122
+ return { type: 'int' as const, value: smaliField.initialValue };
3123
+ }
3124
+
3125
+ // BigInt values for long (J) types
3126
+ if (typeof smaliField.initialValue === 'bigint') {
3127
+ return { type: 'long' as const, value: smaliField.initialValue };
3128
+ }
3129
+
3130
+ // String values should be stored as string type
3131
+ if (typeof smaliField.initialValue === 'string') {
3132
+ return { type: 'string' as const, value: smaliField.initialValue };
3133
+ }
3134
+
3135
+ // Boolean true is a non-default value and should be in staticValues
3136
+ if (smaliField.initialValue === true) {
3137
+ return { type: 'boolean' as const, value: true };
3138
+ }
3139
+
3140
+ // Boolean false and null are default values
3141
+ // For boolean fields with explicit false, return false
3142
+ if (smaliField.initialValue === false && fieldType === 'Z') {
3143
+ return { type: 'boolean' as const, value: false };
3144
+ }
3145
+
3146
+ // For null or other default values, return null
3147
+ return { type: 'null' as const, value: null };
3148
+ });
3149
+ const fields = {
3150
+ staticFields: smaliFields?.staticFields.map(({ field }) => field) ?? [],
3151
+ instanceFields: smaliFields?.instanceFields.map(({ field }) => field) ?? [],
3152
+ };
3153
+
3154
+ // Sort fields to match DEX file order (by field name, then type)
3155
+ // This matches the field order in class_data_item in DEX files
3156
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
3157
+ const sortFields = (fieldsList: DalvikExecutableFieldWithAccess[]) => {
3158
+ fieldsList.sort((a, b) => {
3159
+ // First by field name (case-sensitive comparison)
3160
+ if (a.field.name !== b.field.name) {
3161
+ return a.field.name < b.field.name ? -1 : 1;
3162
+ }
3163
+ // Then by field type (case-sensitive comparison)
3164
+ return a.field.type < b.field.type ? -1 : 1;
3165
+ });
3166
+ };
3167
+
3168
+ sortFields(fields.staticFields);
3169
+ sortFields(fields.instanceFields);
3170
+
3171
+ const annotations: DalvikExecutableClassAnnotations = {
3172
+ classAnnotations: (classAnnotations ?? []).map(annotation => ({
3173
+ type: annotation.type,
3174
+ visibility: annotation.visibility,
3175
+ elements: annotation.elements.map(element => ({
3176
+ name: element.name,
3177
+ value: convertToTaggedEncodedValue(element.value),
3178
+ })),
1159
3179
  })),
1160
- },
1161
- interfaces,
1162
- staticValues: [], // TODO
1163
- }) as any, // TODO
3180
+ fieldAnnotations: [],
3181
+ methodAnnotations: methods.methodAnnotations,
3182
+ parameterAnnotations: methods.parameterAnnotations,
3183
+ };
3184
+
3185
+ for (const smaliField of [ ...(smaliFields?.staticFields ?? []), ...(smaliFields?.instanceFields ?? []) ]) {
3186
+ if (smaliField.annotations.length === 0) {
3187
+ continue;
3188
+ }
3189
+
3190
+ const existingFieldAnnotations = annotations.fieldAnnotations.find(fieldAnnotation => dalvikExecutableFieldEquals(
3191
+ fieldAnnotation.field,
3192
+ smaliField.field.field,
3193
+ ));
3194
+
3195
+ if (existingFieldAnnotations) {
3196
+ existingFieldAnnotations.annotations ??= [];
3197
+ existingFieldAnnotations.annotations.push(...smaliField.annotations.map(annotation => ({
3198
+ type: annotation.type,
3199
+ visibility: annotation.visibility,
3200
+ elements: annotation.elements.map(element => ({
3201
+ name: element.name,
3202
+ value: convertToTaggedEncodedValue(element.value),
3203
+ })),
3204
+ })));
3205
+ continue;
3206
+ }
3207
+
3208
+ annotations.fieldAnnotations.push({
3209
+ field: smaliField.field.field,
3210
+ annotations: smaliField.annotations.map(annotation => ({
3211
+ type: annotation.type,
3212
+ visibility: annotation.visibility,
3213
+ elements: annotation.elements.map(element => ({
3214
+ name: element.name,
3215
+ value: convertToTaggedEncodedValue(element.value),
3216
+ })),
3217
+ })),
3218
+ });
3219
+ }
3220
+
3221
+ // Sort field annotations to match DEX file order (by field name, then type)
3222
+ // This matches the annotations_directory_item field_annotations order in DEX files
3223
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
3224
+ annotations.fieldAnnotations.sort((a, b) => {
3225
+ // First by field name (case-sensitive comparison)
3226
+ if (a.field.name !== b.field.name) {
3227
+ return a.field.name < b.field.name ? -1 : 1;
3228
+ }
3229
+ // Then by field type (case-sensitive comparison)
3230
+ return a.field.type < b.field.type ? -1 : 1;
3231
+ });
3232
+
3233
+ // Compute synthetic flag from members if not explicitly set
3234
+ // This matches baksmali behavior where synthetic flag at class level is not output
3235
+ // but can be inferred from all members being synthetic
3236
+ const allMembers = [
3237
+ ...fields.staticFields,
3238
+ ...fields.instanceFields,
3239
+ ...methods.directMethods,
3240
+ // Note: virtualMethods are not included, matching DEX parser behavior
3241
+ ];
3242
+
3243
+ const allMembersAreSynthetic = (
3244
+ allMembers.every(member => member.accessFlags.synthetic)
3245
+ && allMembers.length > 0
3246
+ );
3247
+
3248
+ const finalAccessFlags = {
3249
+ ...accessFlags,
3250
+ // Use the synthetic flag from the class declaration, or compute it from members if not set
3251
+ synthetic: accessFlags.synthetic || allMembersAreSynthetic,
3252
+ };
3253
+
3254
+ return {
3255
+ accessFlags: finalAccessFlags,
3256
+ class: class_,
3257
+ superclass,
3258
+ sourceFile,
3259
+ annotations: (
3260
+ (
3261
+ annotations.classAnnotations?.length
3262
+ || annotations.fieldAnnotations?.length
3263
+ || annotations.methodAnnotations?.length
3264
+ || annotations.parameterAnnotations?.length
3265
+ )
3266
+ ? ({
3267
+ classAnnotations: annotations.classAnnotations,
3268
+ fieldAnnotations: annotations.fieldAnnotations.map(fieldAnnotation => ({
3269
+ ...fieldAnnotation,
3270
+ field: {
3271
+ ...fieldAnnotation.field,
3272
+ class: class_,
3273
+ },
3274
+ })),
3275
+ methodAnnotations: annotations.methodAnnotations
3276
+ .map(methodAnnotation => ({
3277
+ ...methodAnnotation,
3278
+ method: {
3279
+ ...methodAnnotation.method,
3280
+ class: class_,
3281
+ },
3282
+ }))
3283
+ // Sort method annotations to match DEX file order (lexicographic by method name, then prototype shorty)
3284
+ // This matches the method_idx order in the DEX file's method_id table
3285
+ .sort((a, b) => {
3286
+ // Sort by method name first (using code point comparison, not locale-aware)
3287
+ if (a.method.name !== b.method.name) {
3288
+ return a.method.name < b.method.name ? -1 : 1;
3289
+ }
3290
+ // Then by shorty (prototype signature)
3291
+ return a.method.prototype.shorty < b.method.prototype.shorty ? -1 : 1;
3292
+ }),
3293
+ parameterAnnotations: annotations.parameterAnnotations.map(parameterAnnotation => ({
3294
+ ...parameterAnnotation,
3295
+ method: {
3296
+ ...parameterAnnotation.method,
3297
+ class: class_,
3298
+ },
3299
+ })),
3300
+ })
3301
+ : undefined
3302
+ ),
3303
+ classData: (
3304
+ (
3305
+ methods.directMethods.length > 0
3306
+ || methods.virtualMethods.length > 0
3307
+ || (fields?.staticFields.length ?? 0)
3308
+ || (fields?.instanceFields.length ?? 0)
3309
+ )
3310
+ ? ({
3311
+ directMethods: methods.directMethods.map(method => ({
3312
+ ...method,
3313
+ method: {
3314
+ ...method.method,
3315
+ class: class_,
3316
+ },
3317
+ })),
3318
+ virtualMethods: methods.virtualMethods.map(method => ({
3319
+ ...method,
3320
+ method: {
3321
+ ...method.method,
3322
+ class: class_,
3323
+ },
3324
+ })),
3325
+ staticFields: (fields?.staticFields ?? []).map(field => ({
3326
+ ...field,
3327
+ field: {
3328
+ ...field.field,
3329
+ class: class_,
3330
+ },
3331
+ })),
3332
+ instanceFields: (fields?.instanceFields ?? []).map(field => ({
3333
+ ...field,
3334
+ field: {
3335
+ ...field.field,
3336
+ class: class_,
3337
+ },
3338
+ })),
3339
+ })
3340
+ : undefined
3341
+ ),
3342
+ interfaces: interfaces ?? [],
3343
+ staticValues,
3344
+ };
3345
+ },
1164
3346
  );
1165
3347
 
1166
3348
  setParserName(smaliParser, 'smaliParser');