diff --git a/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/page/CodeView.kt b/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/page/CodeView.kt index 2b98860..ca983e3 100644 --- a/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/page/CodeView.kt +++ b/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/page/CodeView.kt @@ -32,6 +32,7 @@ import me.yricky.oh.abcd.cfm.FieldType import me.yricky.oh.abcd.cfm.MethodTag import me.yricky.oh.abcd.code.Code import me.yricky.oh.abcd.code.TryBlock +import me.yricky.oh.abcd.decompiler.ToJs import me.yricky.oh.abcd.isa.* import me.yricky.oh.abcd.isa.util.BaseInstParser import me.yricky.oh.abcd.isa.util.BaseInstParser.ANNO_ASM_NAME @@ -329,6 +330,71 @@ class CodeView(val code: Code,override val hap:HapSession):AttachHapPage() { } } + }, composeSelectContent { _: Boolean -> + Image(Icons.javaScript(), null, Modifier.fillMaxSize()) + } to composeContent { + Box( + Modifier.fillMaxSize().padding(end = 8.dp, bottom = 8.dp, top = 8.dp) + .clip(RoundedCornerShape(8.dp)) + .border(2.dp, MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(8.dp)) + .padding(8.dp) + ) { + MultiNodeSelectionContainer { + val range = multiNodeSelectionState.rememberSelectionChange() + val dcmpTexts = remember { + listOf(kotlin.runCatching { ToJs(code.asm).toJS() }.getOrElse { "${it.message}\n${it.stackTraceToString()}" }) + } + //处理文本选择等操作 + LaunchedEffect(null){ + textActionFlow.collectLatest { when(it){ + is TextAction.Copy -> { + clipboardManager.setText(buildAnnotatedString { + dcmpTexts.forEachIndexed { index, asmStr -> + it.range.rangeOf(index,asmStr)?.let { r -> + append(asmStr.subSequence(r.start,r.endExclusive)) + append('\n') + } + } + }) + } + is TextAction.SelectAll -> { + multiNodeSelectionState.selectedFrom = MultiNodeSelectionState.SelectionBound.Zero + multiNodeSelectionState.selectedTo = MultiNodeSelectionState.SelectionBound.from(dcmpTexts.size,dcmpTexts.lastOrNull()?.length ?: 0) + } + else -> { } + } } + } + LazyColumnWithScrollBar { + itemsIndexed(dcmpTexts) { index, str -> + Row { + val layoutResult = remember { mutableStateOf(null) } + val selectColor = LocalTextSelectionColors.current.backgroundColor + val thisRange = range?.rangeOf(index, str) + Column(Modifier.fillMaxSize()) { + Text( + text = remember(thisRange) { + val sp = mutableListOf>() + if(thisRange != null){ + sp.add(AnnotatedString.Range( + SpanStyle(background = selectColor), + thisRange.start, + thisRange.endExclusive, + )) + } + AnnotatedString(str, sp, emptyList(),) + }, + style = codeStyle, + modifier = Modifier + .withMultiNodeSelection({ layoutResult.value }, index) + .fillMaxWidth(), + onTextLayout = { layoutResult.value = it }, + ) + } + } + } + } + } + } }, composeSelectContent { _: Boolean -> Image(Icons.listFiles(), null, Modifier.fillMaxSize().alpha(0.5f), colorFilter = grayColorFilter) } to composeContent { diff --git a/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/theme.kt b/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/theme.kt index dc2ca53..6d4084a 100644 --- a/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/theme.kt +++ b/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/theme.kt @@ -287,6 +287,13 @@ object Icons{ } else { painterResource("ic/greyKey/greyKey.svg") } + + @Composable + fun javaScript() = if (isDarkTheme()) { + painterResource("ic/javaScript/javaScript_dark.svg") + } else { + painterResource("ic/javaScript/javaScript.svg") + } } @Composable diff --git a/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/utils.kt b/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/utils.kt index b8f193a..c40d0b2 100644 --- a/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/utils.kt +++ b/abcdecoder/src/jvmMain/kotlin/me/yricky/abcde/ui/utils.kt @@ -318,20 +318,4 @@ fun MethodItem.defineStr(showClass:Boolean = false):String = run { sb.append(name) sb.append(argsStr()) sb.toString() -} - -fun MethodItem.argsStr():String{ - val sb = StringBuilder() - if(this is AbcMethod && codeItem != null){ - val code = codeItem!! - val argCount = code.numArgs - 3 - if(argCount >= 0){ - sb.append("(FunctionObject, NewTarget, this") - repeat(argCount){ - sb.append(", arg$it") - } - sb.append(')') - } - } - return sb.toString() } \ No newline at end of file diff --git a/abcdecoder/src/jvmMain/resources/ic/javaScript/javaScript.svg b/abcdecoder/src/jvmMain/resources/ic/javaScript/javaScript.svg new file mode 100644 index 0000000..d7eb6cc --- /dev/null +++ b/abcdecoder/src/jvmMain/resources/ic/javaScript/javaScript.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/abcdecoder/src/jvmMain/resources/ic/javaScript/javaScript_dark.svg b/abcdecoder/src/jvmMain/resources/ic/javaScript/javaScript_dark.svg new file mode 100644 index 0000000..1886170 --- /dev/null +++ b/abcdecoder/src/jvmMain/resources/ic/javaScript/javaScript_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/cfm/MethodItem.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/cfm/MethodItem.kt index 24b04f2..7dcb5c9 100644 --- a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/cfm/MethodItem.kt +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/cfm/MethodItem.kt @@ -49,7 +49,7 @@ class AbcMethod(abc: AbcBuf, offset: Int) :MethodItem(abc, offset){ } val data:List get() = _data.value - val codeItem: Code? by lazy { + val codeItem: Code? by WeakLazy { data.firstOrNull { it is MethodTag.Code }?.let { Code(this,(it as MethodTag.Code).offset) } } @@ -189,8 +189,10 @@ sealed class MethodTag{ class RuntimeParamAnno(annoOffset: Int) :ParamAnnoTag(annoOffset) class DbgInfo(method: AbcMethod, offset:Int): MethodTag(){ val info = DebugInfo(method.abc,offset) - val state = kotlin.runCatching { info.lineNumberProgram?.eval(info) } - .onFailure { it.printStackTrace() }.getOrNull() + val state by lazy { + kotlin.runCatching { info.lineNumberProgram?.eval(info) } + .onFailure { it.printStackTrace() }.getOrNull() + } override fun toString(): String { return "Dbg(lineStart=${info.lineStart},paramName=${info.params},cps=${info.constantPool},lnp=${info.lineNumberProgram?.eval(info)})" @@ -223,4 +225,20 @@ sealed class MethodTag{ } } } +} + +fun MethodItem.argsStr():String{ + val sb = StringBuilder() + if(this is AbcMethod && codeItem != null){ + val code = codeItem!! + val argCount = code.numArgs - 3 + if(argCount >= 0){ + sb.append("(FunctionObject, NewTarget, this") + repeat(argCount){ + sb.append(", arg$it") + } + sb.append(')') + } + } + return sb.toString() } \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/CodeSegment.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/CodeSegment.kt new file mode 100644 index 0000000..dfd2a36 --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/CodeSegment.kt @@ -0,0 +1,228 @@ +package me.yricky.oh.abcd.decompiler + +import me.yricky.oh.abcd.decompiler.behaviour.Operation +import me.yricky.oh.abcd.isa.Asm + +sealed interface CodeSegment{ + sealed interface BasicBlock:CodeSegment + + //代码段的首个item + val item: Asm.AsmItem + val itemCount: Int + /** + * 代码段的后继代码段 + * 对于[AsLinear],该值表示执行完本代码块后将执行的下一个代码块的codeOffset,因此[AsLinear.isTail]为真时该值无意义; + * 对于[Condition],该值表示未跳转情况下的下一个代码块offset + */ + val next: Int + + sealed class AsLinear(override val item: Asm.AsmItem,override val itemCount: Int,override val next: Int) :CodeSegment{ + + //为true表示不应有后继结点 + abstract fun isTail():Boolean + } + class IfPattern( + val jumpCondition: InsCondition, + //不跳转的时候执行的内容 + val body:AsLinear + ):AsLinear(jumpCondition.item,jumpCondition.itemCount + body.itemCount, body.next){ + init { + assert(jumpCondition.jmpTo == body.next) + assert(jumpCondition.next == body.item.codeOffset) + } + + override fun isTail(): Boolean = false + + override fun toString(): String { + return "if(0x${jumpCondition.item.codeOffset.toString(16)}){ $body }" + } + } + class IfElsePattern( + val condition: InsCondition, + val ifBody:AsLinear, + val elseBody:AsLinear + ):AsLinear(condition.item,condition.itemCount + ifBody.itemCount + elseBody.itemCount, if(elseBody.isTail()) ifBody.next else elseBody.next){ + override fun isTail(): Boolean = ifBody.isTail() && elseBody.isTail() + + override fun toString(): String { + return "if(0x${condition.item.codeOffset.toString(16)}){ $ifBody }else{ $elseBody }" + } + + init { + assert(condition.jmpTo == ifBody.item.codeOffset) + assert(condition.next == elseBody.item.codeOffset) +// assert(ifBody.next == elseBody.next) + } + } + class WhilePattern( + val condition: InsCondition, + val whileBody:AsLinear + ):AsLinear(whileBody.item,condition.itemCount + whileBody.itemCount, condition.next){ + init { + assert(condition.jmpTo == whileBody.item.codeOffset) + assert(whileBody.next == condition.item.codeOffset) + } + override fun isTail(): Boolean = false + + + override fun toString(): String { + return "while(0x${condition.item.codeOffset.toString(16)}){$whileBody}" + } + } + class LoopPattern( + val loopBody:AsLinear + ):AsLinear(loopBody.item,loopBody.itemCount,loopBody.next){ + init { + assert(loopBody.next == loopBody.item.codeOffset) + } + + override fun isTail(): Boolean = true + override fun toString(): String { + return "loop{ $loopBody }" + } + } + class LoopBreakPattern( + val body1:AsLinear, + val breakCondition:InsCondition, + val body2:AsLinear + ):AsLinear(body1.item,body1.itemCount + breakCondition.itemCount + body2.itemCount, breakCondition.jmpTo){ + override fun isTail(): Boolean = false + override fun toString(): String { + return "loop{ $body1 > breakIf(0x${breakCondition.item.codeOffset.toString(16)} to 0x${breakCondition.jmpTo.toString(16)}) > $body2 }" + } + } + class LinearPattern( + val l1:AsLinear, + val l2:AsLinear + ):AsLinear(l1.item, l1.itemCount + l2.itemCount, l2.next){ + override fun isTail(): Boolean = l2.isTail() + + override fun toString(): String { + return "$l1 -> $l2" + } + } + class Linear(item: Asm.AsmItem, itemCount: Int, next: Int) :AsLinear(item, itemCount, next),BasicBlock{ + override fun isTail(): Boolean = false + + override fun toString(): String { + return StringBuilder().apply { + append("(${item.index}-${item.index + itemCount - 1} ") + append("then 0x${next.toString(16)})") + }.toString() + } + } + + class JumpMark(item: Asm.AsmItem): AsLinear(item,1,(item.operation as Operation.Jump).offset + item.codeOffset),BasicBlock { + override fun isTail(): Boolean = false + } + + class Return(item: Asm.AsmItem):AsLinear(item, 1, item.nextOffset),BasicBlock{ + override fun isTail(): Boolean = true + + override fun toString(): String { + return "return(0x${item.codeOffset.toString(16)},${item.index})" + } + } + + + /** + * 当满足条件的时候进行跳转 + * @param condition [Operation.JumpIf.condition] + */ + class InsCondition( + override val item: Asm.AsmItem, + val condition: Operation.Expression, + val jmpTo: Int + ) :BasicBlock{ + override val itemCount: Int = 1 + override val next: Int get() = item.nextOffset + + override fun toString(): String { + return "insCondition(nxt:0x${next.toString(16)},jmp:0x${jmpTo.toString(16)})" + } + } + + + companion object{ + fun genGraph(asm:Asm):Map{ + /* + * 字节码的执行顺序可以用一张有向图表示,图的边就是顺序执行的字节码片段,节点则是分支或汇入的字节码位置。 + * 这一步的操作是找出图的所有节点 + */ + val pcItemMap = mutableMapOf() + //首个字节码的位置显然是一个节点 + pcItemMap[asm.list.first().codeOffset] = asm.list.first() + asm.list.forEach { item -> + val operation = item.operation + if(operation is Operation.Return){ + //返回处的字节码显然是一个节点 + pcItemMap[item.codeOffset] = item + item.next?.let { nxt -> + pcItemMap[nxt.codeOffset] = nxt + } + } else if(operation is Operation.Jump) { + //无条件跳转的目标位置显然是一个节点,无条件跳转的字节码的下一个位置若想被执行,一定是从其他地方跳转到这里,因此也是一个节点。但其本身的位置不应成为一个节点。 + pcItemMap[item.codeOffset] = item + item.next?.let { nxt -> pcItemMap[nxt.codeOffset] = nxt } + val jmpTargetOff = item.codeOffset + operation.offset + pcItemMap[jmpTargetOff] = asm.list.first { i -> i.codeOffset == jmpTargetOff } + } else if(operation is Operation.JumpIf){ + //条件跳转的目标位置、字节码的下一个位置、和其本身的位置都视为一个节点 + pcItemMap[item.codeOffset] = item + pcItemMap[item.nextOffset] = item.next!! + val jmpTargetOff = item.codeOffset + operation.offset + pcItemMap[jmpTargetOff] = asm.list.first { i -> i.codeOffset == jmpTargetOff } + } + } + + val sortedList = pcItemMap.values.sortedBy { it.codeOffset } + + /* + * 现在我们有了节点,接下来从节点构建边。 + * 边有两种,一种唯一指向另一条边,另一种(末端是jumpIf)唯一指向另两条边 + */ + val codeSegmentMap = mutableMapOf() + sortedList.forEachIndexed { index, curr -> + val nxt = sortedList.getOrNull(index + 1) + val ope = curr.operation + if(ope is Operation.Return){ + codeSegmentMap[curr.codeOffset] = Return(curr) + } else if(ope is Operation.JumpIf) { + assert(curr.nextOffset == nxt!!.codeOffset) + codeSegmentMap[curr.codeOffset] = InsCondition( + curr, ope.condition, curr.codeOffset + ope.offset + ) + } else if(ope is Operation.Jump){ +// val len = (nxt?.index ?: asm.list.size) - curr.index +// assert(len == 1) + codeSegmentMap[curr.codeOffset] = JumpMark(curr) + } else { + val len = (nxt?.index ?: asm.list.size) - curr.index + val last = asm.list[curr.index + len - 1] + val next = nxt?.codeOffset ?: last.nextOffset + codeSegmentMap[curr.codeOffset] = Linear(curr,len,next) + } + } + return codeSegmentMap + } + + fun genLinear(asm: Asm):AsLinear{ + val codeSegmentMap = genGraph(asm) + return linearize(codeSegmentMap) + } + + private fun linearize(codeSegments:Map):AsLinear{ + var lastCodeSegments = codeSegments + var linearized:Boolean = false + do { + val (merged, newCodeSegments) = Linearizer1().linearize(lastCodeSegments) + linearized = newCodeSegments.size == 1 && newCodeSegments[0] is AsLinear + lastCodeSegments = newCodeSegments + } while (!linearized && merged) + return if(linearized){ + lastCodeSegments[0] as AsLinear + } else throw IllegalStateException("无法将字节码转化为线性结构,这可能是由于此方法中包含try-catch") + } + } + +} \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/Linearizer.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/Linearizer.kt new file mode 100644 index 0000000..59fb6bb --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/Linearizer.kt @@ -0,0 +1,191 @@ +package me.yricky.oh.abcd.decompiler + +import me.yricky.oh.abcd.decompiler.CodeSegment.LoopPattern +import me.yricky.oh.abcd.isa.asmName + +/** + * 线性化器,消除代码段中的分支结构 + */ +abstract class Linearizer { + fun linearize(lastCodeSegments: Map): Pair> { + var merged = false + val newCodeSegments = mutableMapOf() + val usedSet = mutableSetOf() //这些offset表示的代码块已经包含在newCodeSegments中了 + val transparentSet = mutableSetOf() //usedSet的子集,这些代码块本轮未做处理 + val offsetRefMap = buildRefMap(lastCodeSegments) + lastCodeSegments.forEach{ (_,seg) -> + if (usedSet.contains(seg.item.codeOffset)){ + //这个代码块被合并了 + } else { + usedSet.add(seg.item.codeOffset) + merged = merged or handle( + seg,offsetRefMap,usedSet,transparentSet,lastCodeSegments,newCodeSegments + ){ + handleSegUsed(it,usedSet, transparentSet, newCodeSegments, offsetRefMap) + } + } + } + return Pair(merged,newCodeSegments) + } + + protected abstract fun handle( + seg:CodeSegment, + offsetRefMap: Map, + usedSet: MutableSet, + transparentSet: MutableSet, + lastCodeSegments: Map, + newCodeSegments: MutableMap, + segUsed:(CodeSegment) -> Unit + ):Boolean + + companion object{ + + /** + * 处理一个代码块被使用了 + * “被使用”的定义:下一轮中,该代码块的被引用数-1 + */ + fun handleSegUsed( + seg: CodeSegment, + usedSet:MutableSet, + transparentSet:MutableSet, + newCodeSegments:MutableMap, + offsetRefMap:Map + ){ + assert(transparentSet.contains(seg.item.codeOffset) || !usedSet.contains(seg.item.codeOffset)) + val refCount = offsetRefMap[seg.item.codeOffset]!! +// println("handle:0x${seg.item.codeOffset.toString(16)}, ref:$refCount") + //这个代码被使用了,因此从transparentSet中移除先 + if(transparentSet.remove(seg.item.codeOffset)){ + if(refCount == 1){ //引用数为1,代表仅被本次使用的地方引用,这个代码块不应再带到下一轮中 + newCodeSegments.remove(seg.item.codeOffset) + } + } else { //移除失败了,证明这个代码块还没被迭代到 + usedSet.add(seg.item.codeOffset) //移到被使用set中,防止改代码块被迭代 + if(refCount > 1){ //引用数大于1,代表还有其他地方引用,这个代码块应带到下一轮中 + newCodeSegments[seg.item.codeOffset] = seg + } + } + } + + + /** + * 如果代码块A执行后可能会执行代码块B,则代码块B的codeOffset对应的refCount加1。 + * 这个函数按照这样的规则构建一个codeOffset->refCount的map + * + * @return Map + */ + fun buildRefMap( + codeSegments: Map, + ):Map{ + val offsetRefMap = mutableMapOf() + codeSegments.forEach{ (_, seg) -> + val nxt = codeSegments[seg.next] + if((seg as? CodeSegment.AsLinear)?.isTail() == true){ + //没有后继结点,不会引用其他代码块 + }else if(nxt == null){ + if(seg.next != seg.item.asm.list.last().nextOffset){ + throw IllegalStateException("not valid next,nxt:0x${seg.next.toString(16)},item:${seg.item.asmName},segments:${codeSegments}") + } + } else { + offsetRefMap[nxt.item.codeOffset] = (offsetRefMap[nxt.item.codeOffset]?:0) + 1 + } + if(seg is CodeSegment.InsCondition){ +// println("$seg, ${seg.jmpTo}, context:${seg.item.asm.code.method.name}") + val jmp = codeSegments[seg.jmpTo]!! + offsetRefMap[jmp.item.codeOffset] = (offsetRefMap[jmp.item.codeOffset]?:0) + 1 + } + } + return offsetRefMap + } + } +} + +class Linearizer1: Linearizer() { + override fun handle( + seg:CodeSegment, + offsetRefMap: Map, + usedSet: MutableSet, + transparentSet: MutableSet, + lastCodeSegments: Map, + newCodeSegments: MutableMap, + segUsed:(CodeSegment) -> Unit + ):Boolean{ + var merged = false + val nxt = lastCodeSegments[seg.next] + if(seg is CodeSegment.AsLinear){ + if(seg.isTail()){ //这个代码块就是终点了,暂时原样交给后续轮次 + newCodeSegments[seg.item.codeOffset] = seg + transparentSet.add(seg.item.codeOffset) + } else if(seg.next == seg.item.codeOffset && seg !is LoopPattern) { //代码段的下个节点指向自己,无限循环 + val newLinear = LoopPattern(seg) + newCodeSegments[seg.item.codeOffset] = newLinear + merged = true + }else if( + nxt is CodeSegment.AsLinear && offsetRefMap[nxt.item.codeOffset] == 1 && + (transparentSet.contains(nxt.item.codeOffset) || !usedSet.contains(nxt.item.codeOffset)) + ){ + val newLinear = CodeSegment.LinearPattern(seg, nxt) + newCodeSegments[seg.item.codeOffset] = newLinear + merged = true + segUsed(nxt) + } else if( + nxt is CodeSegment.InsCondition && offsetRefMap[nxt.item.codeOffset] == 1 && nxt.jmpTo == seg.item.codeOffset && + (transparentSet.contains(nxt.item.codeOffset) || !usedSet.contains(nxt.item.codeOffset)) + ){ + val newLinear = CodeSegment.WhilePattern(nxt, seg) + newCodeSegments[seg.item.codeOffset] = newLinear + merged = true + segUsed(nxt) + } + else if( + nxt is CodeSegment.InsCondition && offsetRefMap[nxt.item.codeOffset] == 1 && + lastCodeSegments[nxt.next] is CodeSegment.AsLinear && offsetRefMap[nxt.next] == 1 && + lastCodeSegments[nxt.next]!!.next == seg.item.codeOffset && + (transparentSet.contains(nxt.item.codeOffset) || !usedSet.contains(nxt.item.codeOffset)) + ){ + val nxtnxt = lastCodeSegments[nxt.next] as CodeSegment.AsLinear + val newLinear = CodeSegment.LoopBreakPattern(seg,nxt,nxtnxt) + newCodeSegments[seg.item.codeOffset] = newLinear + merged = true + segUsed(nxt) + segUsed(nxtnxt) + } + else { //这个代码块本轮不做处理,暂时原样交给后续轮次 + newCodeSegments[seg.item.codeOffset] = seg + transparentSet.add(seg.item.codeOffset) + } + } else if(seg is CodeSegment.InsCondition){ + val jmp = lastCodeSegments[seg.jmpTo]!! + nxt!! + if(nxt is CodeSegment.AsLinear && seg.jmpTo == nxt.next && //offsetRefMap[nxt.item.codeOffset] == 1 && + (transparentSet.contains(nxt.item.codeOffset) || !usedSet.contains(nxt.item.codeOffset)) + ){ + val newLinear = CodeSegment.IfPattern(seg, nxt) + newCodeSegments[seg.item.codeOffset] = newLinear + merged = true + segUsed(nxt) + } else if( + nxt is CodeSegment.AsLinear && //offsetRefMap[nxt.item.codeOffset] == 1 && + jmp is CodeSegment.AsLinear && //offsetRefMap[jmp.item.codeOffset] == 1 && + ((jmp.next == nxt.next) || ((nxt as? CodeSegment.AsLinear)?.isTail() == true) || ((jmp as? CodeSegment.AsLinear)?.isTail() == true)) && + (transparentSet.contains(nxt.item.codeOffset) || !usedSet.contains(nxt.item.codeOffset)) && + (transparentSet.contains(jmp.item.codeOffset) || !usedSet.contains(jmp.item.codeOffset)) + ){ + val newLinear = CodeSegment.IfElsePattern(seg, jmp, nxt) + newCodeSegments[seg.item.codeOffset] = newLinear + merged = true + segUsed(nxt) + segUsed(jmp) + } else if(nxt.item.codeOffset == jmp.item.codeOffset){ + val newLinear = CodeSegment.Linear(seg.item, 1, seg.next) + newCodeSegments[seg.item.codeOffset] = newLinear + merged = true + } else { //这个条件跳转这次没法处理,暂时原样交给后续轮次 + newCodeSegments[seg.item.codeOffset] = seg + transparentSet.add(seg.item.codeOffset) + } + } + return merged + } + +} \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/ToJs.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/ToJs.kt new file mode 100644 index 0000000..61e9f6a --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/ToJs.kt @@ -0,0 +1,224 @@ +package me.yricky.oh.abcd.decompiler + +import me.yricky.oh.abcd.cfm.argsStr +import me.yricky.oh.abcd.decompiler.behaviour.FunSimCtx +import me.yricky.oh.abcd.decompiler.behaviour.JSValue +import me.yricky.oh.abcd.decompiler.behaviour.Operation +import me.yricky.oh.abcd.isa.Asm +import me.yricky.oh.abcd.isa.asmName +import me.yricky.oh.abcd.literal.ModuleLiteralArray + +class ToJs(val asm: Asm) { + class UnImplementedError(item:Asm.AsmItem):Throwable("对字节码${item.asmName}的解析尚未实现") + + fun toJS():String{ + val fc = FunctionDecompilerContext() + val sb = StringBuilder() + sb.append("function ").append(asm.code.method.name).append(asm.code.method.argsStr()).append("{\n") + sb.append(fc.toJS(CodeSegment.genLinear(asm),1)) + sb.append("}") + return ("${fc.imports.joinToString(separator = ";\n") { it.toString() }}\n" + + "${fc.nsImports.joinToString(separator = ";\n") { it.toString() }}\n" + + "\n${sb}").trim() + } + + private fun FunctionDecompilerContext.toJS(op:Operation):String{ + return when(op){ + Operation.Debugger -> "/* debugger */" + Operation.Deprecated -> "/* deprecated */" + Operation.Disabled -> "/* disabled */" + Operation.NOP -> "/* nop */" + is Operation.NewLex -> "/* newLex(${op.size}) */" + is Operation.UnImplemented -> throw UnImplementedError(op.item) + is Operation.JustAnno -> "/* ${op.anno} */" + is Operation.Statement -> { + when(op){ + is Operation.Assign -> "${toJS(op.target)} = ${toJS(op.newValue)};" + is Operation.AssignObj -> "${toJS(op.target)} = ${toJS(op.newValue)};" + is Operation.Jump -> throw IllegalStateException("jump wtf")// "/* jump */" + is Operation.JumpIf -> throw IllegalStateException("jumpIf wtf")//"/* jumpIf */" + is Operation.Return -> "return ${if(op.hasValue) toJS(FunSimCtx.RegId.ACC) else "undefined"};" + Operation.Throw.Acc -> "throw ${toJS(FunSimCtx.RegId.ACC)};" + is Operation.Throw.Error -> "throw Error(${op.msg});" + } + } + + } + } + + private fun FunctionDecompilerContext.toJS(exp:Operation.Expression):String { + return when(exp){ + is Operation.BiExp.AShr -> "${toJS(exp.l)} >> ${toJS(exp.r)}" + is Operation.BiExp.Add -> "${toJS(exp.l)} + ${toJS(exp.r)}" + is Operation.BiExp.And -> "${toJS(exp.l)} & ${toJS(exp.r)}" + is Operation.BiExp.Div -> "${toJS(exp.l)} / ${toJS(exp.r)}" + is Operation.BiExp.Eq -> "${toJS(exp.l)} == ${toJS(exp.r)}" + is Operation.BiExp.Exp -> "${toJS(exp.l)} ** ${toJS(exp.r)}" + is Operation.BiExp.GEq -> "${toJS(exp.l)} >= ${toJS(exp.r)}" + is Operation.BiExp.Ge -> "${toJS(exp.l)} > ${toJS(exp.r)}" + is Operation.BiExp.InstOf -> "${toJS(exp.l)} instanceof ${toJS(exp.r)}" + is Operation.BiExp.IsIn -> "${toJS(exp.l)} in ${toJS(exp.r)}" + is Operation.BiExp.LEq -> "${toJS(exp.l)} <= ${toJS(exp.r)}" + is Operation.BiExp.Less -> "${toJS(exp.l)} < ${toJS(exp.r)}" + is Operation.BiExp.Mod -> "${toJS(exp.l)} % ${toJS(exp.r)}" + is Operation.BiExp.Mul -> "${toJS(exp.l)} * ${toJS(exp.r)}" + is Operation.BiExp.NEq -> "${toJS(exp.l)} != ${toJS(exp.r)}" + is Operation.BiExp.Or -> "${toJS(exp.l)} | ${toJS(exp.r)}" + is Operation.BiExp.Shl -> "${toJS(exp.l)} << ${toJS(exp.r)}" + is Operation.BiExp.Shr -> "${toJS(exp.l)} >>> ${toJS(exp.r)}" + is Operation.BiExp.StrictEq -> "${toJS(exp.l)} === ${toJS(exp.r)}" + is Operation.BiExp.StrictNEq -> "${toJS(exp.l)} !== ${toJS(exp.r)}" + is Operation.BiExp.Sub -> "${toJS(exp.l)} - ${toJS(exp.r)}" + is Operation.BiExp.Xor -> "${toJS(exp.l)} ^ ${toJS(exp.r)}" + is Operation.CallAcc -> "${ exp.overrideThis?.let { toJS(it) } ?: "this" }.${toJS(FunSimCtx.RegId.ACC)}(${exp.args.joinToString { toJS(it) }})" + Operation.DynamicImport -> "import(${toJS(FunSimCtx.RegId.ACC)})" + is Operation.JustImm -> toJS(exp.value) + is Operation.LoadExternalModule -> "${exp.ext.also { imports.add(it) }.localName}" + is Operation.LoadReg -> toJS(exp.regId) + is Operation.NewClass -> TODO("解析NewClass操作尚未实现") + is Operation.NewInst -> "new ${toJS(exp.clazz)}(${exp.constructorArgs.joinToString { toJS(it) }})" + is Operation.ObjField.Index -> "${toJS(exp.obj)}[${exp.index}]" + is Operation.ObjField.Name -> "${toJS(exp.obj)}.${exp.name}" + is Operation.ObjField.Value -> "${toJS(exp.obj)}[${toJS(exp.value)}]" + is Operation.UaExp.Dec -> "${toJS(exp.source)} - 1" + is Operation.GetModuleNamespace -> "import(${exp.ns.str})" + is Operation.UaExp.GetTemplateObject -> TODO("解析GetTemplateObject尚未实现") + is Operation.UaExp.Inc -> "${toJS(exp.source)} + 1" + is Operation.UaExp.IsFalse -> "${toJS(exp.source)} == false" + is Operation.UaExp.IsTrue -> "${toJS(exp.source)} == true" + is Operation.UaExp.Neg -> "-${toJS(exp.source)}" + is Operation.UaExp.Not -> "~${toJS(exp.source)}" + is Operation.UaExp.ToNumber -> "ToNumber(${toJS(exp.source)})" + is Operation.UaExp.ToNumeric -> "ToNumeric(${toJS(exp.source)})" + is Operation.UaExp.TypeOf -> "typeof(${toJS(exp.source)})" + } + } + + /** + * [CodeSegment.IfPattern]的条件跳转意味着如果跳转,则***不执行***body,因此在转换成js代码时,需要对条件判断的表达式取反 + */ + private fun FunctionDecompilerContext.oppositeJS(exp:Operation.Expression):String{ + return when(exp){ + is Operation.UaExp.IsTrue -> toJS(Operation.UaExp.IsFalse(exp.source)) + is Operation.UaExp.IsFalse -> toJS(Operation.UaExp.IsTrue(exp.source)) + is Operation.BiExp.Eq -> toJS(Operation.BiExp.NEq(exp.l,exp.r)) + is Operation.BiExp.NEq -> toJS(Operation.BiExp.Eq(exp.l,exp.r)) + is Operation.BiExp.StrictEq -> toJS(Operation.BiExp.StrictNEq(exp.l,exp.r)) + is Operation.BiExp.StrictNEq -> toJS(Operation.BiExp.StrictEq(exp.l,exp.r)) + else -> "!(${toJS(exp)})" + } + } + + fun toJS(jsValue: JSValue):String{ + return when(jsValue){ + is JSValue.ArrInst -> jsValue.content.joinToString(",","[","]") { toJS(it) } + is JSValue.ClassObj -> TODO("尚未实现的toJS操作") + is JSValue.Error -> "Error(...)" + JSValue.False -> "false" + is JSValue.Function -> "function ${jsValue.method.name}()" + JSValue.Hole -> "undefined /* hole */" + JSValue.Infinity -> "infinity" + JSValue.Nan -> "NaN" + JSValue.Null -> "null" + is JSValue.Number -> jsValue.value.toString() + is JSValue.ObjInst -> if(jsValue.content.isEmpty()) "{}" else jsValue.content.asSequence().joinToString(", ","{","}") { + "${it.key}:${toJS(it.value)}" + } + is JSValue.Str -> "\"${jsValue.value}\"" + JSValue.Symbol.Iterator -> "Symbol.iterator" + JSValue.Symbol.SymbolObj -> "Symbol" + JSValue.True -> "true" + JSValue.Undefined -> "undefined" + } + } + + //以\n结尾 + private fun FunctionDecompilerContext.toJS(linear:CodeSegment.AsLinear, indent:Int = 1):String{ + return when(linear){ + is CodeSegment.IfElsePattern -> { + val sb = StringBuilder() + sb.append(" ".repeat(indent)).append("if(${toJS(linear.condition.condition)}){\n") + sb.append(toJS(linear.ifBody, indent + 1)) + sb.append(" ".repeat(indent)).append("} else {\n") + sb.append(toJS(linear.elseBody, indent + 1)) + sb.append(" ".repeat(indent)).append("}\n") + sb.toString() + } + is CodeSegment.IfPattern -> { + val sb = StringBuilder() + sb.append(" ".repeat(indent)).append("if(${oppositeJS(linear.jumpCondition.condition)}){\n") + sb.append(toJS(linear.body, indent + 1)) + sb.append(" ".repeat(indent)).append("}\n") + sb.toString() + } + is CodeSegment.Linear -> { + val sb = StringBuilder() + var item:Asm.AsmItem? = linear.item + repeat(linear.itemCount){ + sb.append(" ".repeat(indent)) + val op = item!!.operation + sb.append(toJS(op)) + sb.append("\n") + item = item?.next + } + sb.toString() + } + is CodeSegment.LinearPattern -> { + val sb = StringBuilder() + sb.append(toJS(linear.l1,indent)) + sb.append(toJS(linear.l2,indent)) + sb.toString() + } + is CodeSegment.LoopBreakPattern -> { + val sb = StringBuilder() + sb.append(" ".repeat(indent)).append("while(true){\n") + sb.append(toJS(linear.body1, indent + 1)) + sb.append(" ".repeat(indent + 1)).append("if(${toJS(linear.breakCondition.condition)}){\n") + sb.append(" ".repeat(indent + 2)).append("break;\n") + sb.append(" ".repeat(indent + 1)).append("}\n") + sb.append(toJS(linear.body2, indent + 1)) + sb.append(" ".repeat(indent)).append("}\n") + sb.toString() + } + is CodeSegment.LoopPattern -> { + val sb = StringBuilder() + sb.append(" ".repeat(indent)).append("while(true){\n") + sb.append(toJS(linear.loopBody, indent + 1)) + sb.append(" ".repeat(indent)).append("}\n") + sb.toString() + } + is CodeSegment.Return -> " ".repeat(indent) + toJS(linear.item.operation) + "\n" + is CodeSegment.WhilePattern -> { + val sb = StringBuilder() + sb.append(" ".repeat(indent)).append("while(${toJS(linear.condition.condition)}){\n") + sb.append(toJS(linear.whileBody, indent + 1)) + sb.append(" ".repeat(indent)).append("}\n") + sb.toString() + } + + is CodeSegment.JumpMark -> "" + } + } + + private fun toJS(regId: FunSimCtx.RegId):String{ + if(regId.isReg()){ + val v = regId.getRegV() + val vRegs = asm.code.numVRegs + return if (v < vRegs) "v${v}" else when(val aIndex = v - vRegs){ + 0L -> "FunctionObject" + 1L -> "NewTarget" + 2L -> "this" + else -> "arg${aIndex - 3}" + } + } else if (regId == FunSimCtx.RegId.GLOBAL) { + return "AtkTsGlobal" + } else if(regId == FunSimCtx.RegId.THIS) { + return "this" + } else return regId.toJS() + } + + private class FunctionDecompilerContext(){ + val imports:MutableList = mutableListOf() + val nsImports:MutableList = mutableListOf() + } +} \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/FunSimCtx.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/FunSimCtx.kt new file mode 100644 index 0000000..48f8786 --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/FunSimCtx.kt @@ -0,0 +1,66 @@ +package me.yricky.oh.abcd.decompiler.behaviour + +import me.yricky.oh.abcd.isa.Asm + +class FunSimCtx( + val asm: Asm.AsmItem, + val lexEnv: LexEnv? = null +) { + /** + * 标记一个可以存储JSValue的位置,如acc、寄存器和词法环境槽位 + */ + @JvmInline + value class RegId(val value: Long){ + companion object{ + val ACC = RegId(0) + val GLOBAL = RegId(0x4000_0000_0000_0001L) + val THIS = RegId(0x4000_0000_0000_0002L) + const val MASK_REG = 0x1000_0000_0000_0000L + const val MASK_LEX = 0x2000_0000_0000_0000L + const val MASK_OTH = 0x4000_0000_0000_0000L + fun regId(reg: Int) = RegId(MASK_REG or reg.toLong()) + fun lexId(lvl:Int,slot:Int) = RegId(MASK_LEX or (lvl.and(0xffff).shl(16) or slot.and(0xffff)).toLong().and(0xffffffff)) + } + + fun isReg() = value and MASK_REG == MASK_REG + fun getRegV() = value xor MASK_REG + + fun toJS(): String { + if(value == ACC.value){ + return "_acc_" + } else if(value == GLOBAL.value){ + return "global" + } else if(value == THIS.value){ + return "this" + } else if(value and MASK_REG == MASK_REG){ + return "__v${value xor MASK_REG}__" + } else if(value and MASK_LEX == MASK_LEX){ + return "__lex${(value and 0xffffffff).toString(16)}__" + } else { + return "unknown${value.toString(16)}" + } +// return super.toString() + } + } + + val registers: Map = emptyMap() + + class LexEnv( + val tag: String, + val parent: LexEnv?, + val sendable: Boolean, + val content: List + ) + + interface Effect{ + /** + * 读取了哪些位置 + */ + fun read(): Sequence = emptySequence() + + /** + * 可能会影响(写入)哪些位置的值 + */ + fun effected(): Sequence = emptySequence() + } +} \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/JSValue.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/JSValue.kt new file mode 100644 index 0000000..4418323 --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/JSValue.kt @@ -0,0 +1,93 @@ +package me.yricky.oh.abcd.decompiler.behaviour + +import me.yricky.oh.abcd.cfm.MethodItem +import me.yricky.oh.abcd.isa.Asm +import me.yricky.oh.abcd.literal.LiteralArray + +sealed interface JSValue { + object Hole: JSValue + + object Undefined: JSValue + object Null: JSValue + object True: JSValue + object False: JSValue + + object Nan: JSValue + object Infinity: JSValue + + class Number(val value: kotlin.Number): JSValue + class ObjInst(val content: Map): JSValue + class ArrInst(val content: List): JSValue + class Str(val value: String): JSValue + + class Function(val method: MethodItem,val argCounts:Int): JSValue + class ClassObj( + val constructor: Function, + val fields: LiteralArray, + val parent: JSValue + ): JSValue + + class Error(): JSValue + + class GlobalObj(val name:String) + + sealed interface Symbol: JSValue { + object SymbolObj: JSValue + object Iterator: Symbol + } + + + companion object{ + const val PROTO = "__proto__" + + fun asObj(asm: Asm, literalArray: LiteralArray):ObjInst { + val objKv = mutableMapOf() + val iter = literalArray.content.iterator() + while (iter.hasNext()){ + val next = iter.next() + if(next is LiteralArray.Literal.Str){ + objKv[next.get(asm.code.abc)] = asJSValue(asm,iter.next()) + } else break + } + return ObjInst(objKv) + } + + fun asArr(asm: Asm, literalArray: LiteralArray):ArrInst { + return ArrInst(literalArray.content.map { asJSValue(asm,it) }) + } + + private fun asJSValue(asm:Asm, literal: LiteralArray.Literal):JSValue { + return when(literal){ + is LiteralArray.Literal.Accessor -> TODO() + is LiteralArray.Literal.Bool -> if(literal.value == 0x00.toByte()) False else True + is LiteralArray.Literal.BuiltinTypeIndex -> TODO() + is LiteralArray.Literal.F32 -> Number(literal.value) + is LiteralArray.Literal.F64 -> Number(literal.value) + is LiteralArray.Literal.I32 -> Number(literal.value) + is LiteralArray.Literal.LiteralBufferIndex -> TODO() + is LiteralArray.Literal.MethodAffiliate -> TODO() + is LiteralArray.Literal.NullValue -> Null + is LiteralArray.Literal.TagValue -> TODO() + is LiteralArray.Literal.ArrayF32 -> TODO() + is LiteralArray.Literal.ArrayF64 -> TODO() + is LiteralArray.Literal.ArrayI16 -> TODO() + is LiteralArray.Literal.ArrayI32 -> TODO() + is LiteralArray.Literal.ArrayI64 -> TODO() + is LiteralArray.Literal.ArrayI8 -> TODO() + is LiteralArray.Literal.ArrayStr -> ArrInst(literal.get(asm.code.abc).map { Str(it) }) + is LiteralArray.Literal.ArrayU1 -> TODO() + is LiteralArray.Literal.ArrayU16 -> TODO() + is LiteralArray.Literal.ArrayU32 -> TODO() + is LiteralArray.Literal.ArrayU64 -> TODO() + is LiteralArray.Literal.ArrayU8 -> TODO() + is LiteralArray.Literal.LiteralArr -> TODO() + is LiteralArray.Literal.AsyncGeneratorMethod -> TODO() + is LiteralArray.Literal.GeneratorMethod -> TODO() + is LiteralArray.Literal.Getter -> TODO() + is LiteralArray.Literal.Method -> TODO() + is LiteralArray.Literal.Setter -> TODO() + is LiteralArray.Literal.Str -> Str(literal.get(asm.code.abc)) + } + } + } +} \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/Operation.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/Operation.kt new file mode 100644 index 0000000..b3114dc --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/Operation.kt @@ -0,0 +1,378 @@ +package me.yricky.oh.abcd.decompiler.behaviour + +import me.yricky.oh.abcd.isa.Asm.AsmItem +import me.yricky.oh.abcd.isa.Inst.Companion.toUnsignedInt +import me.yricky.oh.abcd.isa.InstFmt +import me.yricky.oh.abcd.literal.LiteralArray +import me.yricky.oh.abcd.literal.ModuleLiteralArray +import me.yricky.oh.abcd.literal.OhmUrl + +sealed interface Operation { + companion object { + fun from(item: AsmItem): Operation { + val opCode = item.ins.opCode + item.prefix?.let { prefix -> + return when(prefix){ + 0xfb.toByte() -> when(opCode){ + 0x01.toByte() -> AssignObj(ObjField.Value(regId(item.opUnits[4]), regId(item.opUnits[3])), LoadReg.acc) + 0x02.toByte() -> AssignObj(ObjField.Index(regId(item.opUnits[4]),item.opUnits[3].toUnsignedInt()), LoadReg.acc) + + 0x13.toByte() -> UaExp.IsTrue(LoadReg.acc).st2Acc() + 0x14.toByte() -> UaExp.IsFalse(LoadReg.acc).st2Acc() + else -> UnImplemented(item) + } + 0xfc.toByte() -> Deprecated + 0xfd.toByte() -> when(opCode){ + 0x0a.toByte() -> AssignObj(ObjField.Index(regId(item.opUnits[2]), item.opUnits[3].toUnsignedInt()), LoadReg.acc) + 0x11.toByte() -> LoadExternalModule(item.asm.code.method.clazz.getClass()!!.moduleInfo!!.regularImports[item.opUnits[2].toUnsignedInt()]).st2Acc() + else -> UnImplemented(item) + } + 0xfe.toByte() -> when(opCode){ + 0x01.toByte() -> Throw.Error("notexists") + 0x02.toByte() -> Throw.Error("patternnoncoercible") + 0x03.toByte() -> Throw.Error("deletesuperproperty") + 0x04.toByte() -> Throw.Error("constassignment", "${item.ins.format[2]}") + + 0x09.toByte() -> JustAnno("acc: ${(item.ins.format[2] as InstFmt.SId).getString(item)}") + else -> UnImplemented(item) + } + else -> UnImplemented(item) + } + } + + return when(opCode){ + 0x00.toByte() -> JSValue.Undefined.just().st2Acc() + 0x01.toByte() -> JSValue.Null.just().st2Acc() + 0x02.toByte() -> JSValue.True.just().st2Acc() + 0x03.toByte() -> JSValue.False.just().st2Acc() + 0x04.toByte() -> JSValue.ObjInst(emptyMap()).just().st2Acc() + 0x05.toByte() -> JSValue.ArrInst(emptyList()).just().st2Acc() + 0x06.toByte() -> JSValue.asArr(item.asm,(item.ins.format[2] as InstFmt.LId).getLA(item)).just().st2Acc() + 0x07.toByte() -> JSValue.asObj(item.asm,(item.ins.format[2] as InstFmt.LId).getLA(item)).just().st2Acc() + 0x08.toByte() -> { + val clazz = item.opUnits[3].toUnsignedInt() + val argC = item.opUnits[2].toUnsignedInt() + val args = if(argC.toUnsignedInt() > 1) ((clazz + 1) until (clazz + argC)) else emptyList() + Assign(FunSimCtx.RegId.ACC, NewInst(regId(clazz), args.map { regId(it) })) + } + 0x09.toByte() -> NewLex(item.opUnits[1].toUnsignedInt()) + 0x0a.toByte() -> BiExp.Add(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x0b.toByte() -> BiExp.Sub(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x0c.toByte() -> BiExp.Mul(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x0d.toByte() -> BiExp.Div(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x0e.toByte() -> BiExp.Mod(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x0f.toByte() -> BiExp.Eq(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x10.toByte() -> BiExp.NEq(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x11.toByte() -> BiExp.Less(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x12.toByte() -> BiExp.LEq(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x13.toByte() -> BiExp.Ge(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x14.toByte() -> BiExp.GEq(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x15.toByte() -> BiExp.Shl(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x16.toByte() -> BiExp.Shr(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x17.toByte() -> BiExp.AShr(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x18.toByte() -> BiExp.And(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x19.toByte() -> BiExp.Or(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x1a.toByte() -> BiExp.Xor(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x1b.toByte() -> BiExp.Exp(regId(item.opUnits[2]).ld(), LoadReg.acc).st2Acc() + 0x1c.toByte() -> UaExp.TypeOf(LoadReg.acc).st2Acc() + 0x1d.toByte() -> UaExp.ToNumber(LoadReg.acc).st2Acc() + 0x1e.toByte() -> UaExp.ToNumeric(LoadReg.acc).st2Acc() + 0x1f.toByte() -> UaExp.Neg(LoadReg.acc).st2Acc() + 0x20.toByte() -> UaExp.Not(LoadReg.acc).st2Acc() + 0x21.toByte() -> UaExp.Inc(LoadReg.acc).st2Acc() + 0x22.toByte() -> UaExp.Dec(LoadReg.acc).st2Acc() + 0x23.toByte() -> UaExp.IsTrue(LoadReg.acc).st2Acc() + 0x24.toByte() -> UaExp.IsFalse(LoadReg.acc).st2Acc() + 0x25.toByte() -> BiExp.IsIn(LoadReg(regId(item.opUnits[2])), LoadReg(FunSimCtx.RegId.ACC)).st2Acc() + 0x26.toByte() -> BiExp.InstOf(LoadReg(regId(item.opUnits[2])), LoadReg(FunSimCtx.RegId.ACC)).st2Acc() + 0x27.toByte() -> BiExp.StrictNEq(LoadReg(FunSimCtx.RegId.ACC), LoadReg(regId(item.opUnits[2]))).st2Acc() + 0x28.toByte() -> BiExp.StrictEq(LoadReg(FunSimCtx.RegId.ACC), LoadReg(regId(item.opUnits[2]))).st2Acc() + 0x29.toByte() -> CallAcc().st2Acc() + 0x2a.toByte() -> CallAcc(listOf(regId(item.opUnits[2]))).st2Acc() + 0x2b.toByte() -> CallAcc(listOf( + regId(item.opUnits[2]), + regId(item.opUnits[3].toUnsignedInt()) + )).st2Acc() + 0x2c.toByte() -> CallAcc(listOf( + regId(item.opUnits[2]), + regId(item.opUnits[3].toUnsignedInt()), + regId(item.opUnits[4].toUnsignedInt()) + )).st2Acc() + 0x2d.toByte() -> CallAcc(overrideThis = regId(item.opUnits[2])).st2Acc() + 0x2e.toByte() -> CallAcc(listOf(regId(item.opUnits[3])), regId(item.opUnits[2])).st2Acc() + 0x2f.toByte() -> CallAcc(listOf( + regId(item.opUnits[3]), + regId(item.opUnits[4].toUnsignedInt()) + ), regId(item.opUnits[2]) + ).st2Acc() + 0x30.toByte() -> CallAcc(listOf( + regId(item.opUnits[3]), + regId(item.opUnits[4].toUnsignedInt()), + regId(item.opUnits[5].toUnsignedInt()) + ), regId(item.opUnits[2]) + ).st2Acc() + + 0x33.toByte() -> JSValue.Function( + (item.ins.format[2] as InstFmt.MId).getMethod(item), + item.opUnits[3].toUnsignedInt() + ).just().st2Acc() + + 0x35.toByte() -> NewClass( + JSValue.Function((item.ins.format[2] as InstFmt.MId).getMethod(item), item.opUnits[4].toUnsignedInt()), + (item.ins.format[3] as InstFmt.LId).getLA(item), + regId(item.opUnits[5]) + ).st2Acc() + + 0x37.toByte() -> Assign(LoadReg.ACC,ObjField.Value(regId(item.opUnits[2]), LoadReg.ACC)) + 0x38.toByte() -> AssignObj(ObjField.Value(regId(item.opUnits[2]), regId(item.opUnits[3])), LoadReg.acc) + + 0x3a.toByte() -> Assign(LoadReg.ACC, ObjField.Index(LoadReg.ACC,item.opUnits[2].toUnsignedInt())) + 0x3b.toByte() -> AssignObj(ObjField.Index(regId(item.opUnits[2]), item.opUnits[3].toUnsignedInt()), LoadReg.acc) + 0x3c.toByte() -> LoadReg(FunSimCtx.RegId.lexId(item.opUnits[1].toUnsignedInt(),item.opUnits[2].toUnsignedInt())).st2Acc() + 0x3d.toByte() -> Assign(FunSimCtx.RegId.lexId(item.opUnits[1].toUnsignedInt(),item.opUnits[2].toUnsignedInt()), LoadReg.acc) + 0x3e.toByte() -> JSValue.Str((item.ins.format[1] as InstFmt.SId).getString(item)).just().st2Acc() + 0x3f.toByte() -> ObjField.Name(FunSimCtx.RegId.GLOBAL, (item.ins.format[2] as InstFmt.SId).getString(item)).st2Acc() + 0x40.toByte() -> AssignObj(ObjField.Name(FunSimCtx.RegId.GLOBAL, (item.ins.format[2] as InstFmt.SId).getString(item)),LoadReg.acc) + 0x41.toByte() -> ObjField.Name(FunSimCtx.RegId.GLOBAL, (item.ins.format[2] as InstFmt.SId).getString(item)).st2Acc() + 0x42.toByte() -> ObjField.Name(LoadReg.ACC, (item.ins.format[2] as InstFmt.SId).getString(item)).st2Acc() + 0x43.toByte() -> AssignObj(ObjField.Name(regId(item.opUnits[3]),(item.ins.format[2] as InstFmt.SId).getString(item)), LoadReg.acc) + 0x44.toByte() -> Assign(regId(item.opUnits[1]), regId(item.opUnits[2]).ld()) + 0x45.toByte() -> Assign(regId(item.opUnits[1]), regId(item.opUnits[2]).ld()) + + 0x49.toByte() -> ObjField.Name( + FunSimCtx.RegId.THIS, + (item.ins.format[2] as InstFmt.SId).getString(item) + ).st2Acc() + 0x4a.toByte() -> AssignObj(ObjField.Name(regId(item.opUnits[3]),(item.ins.format[2] as InstFmt.SId).getString(item)), FunSimCtx.RegId.THIS.ld()) + 0x4b.toByte() -> ObjField.Value(FunSimCtx.RegId.THIS, LoadReg.ACC).st2Acc() + 0x4c.toByte() -> AssignObj(ObjField.Value(FunSimCtx.RegId.THIS, regId(item.opUnits[3])), LoadReg.acc) + 0x4d.toByte() -> Jump(item.opUnits[1].toInt()) + 0x4e.toByte() -> Jump(item.opUnits[1].toInt()) + 0x4f.toByte() -> JumpIf(item.opUnits[1].toInt(), BiExp.Eq(LoadReg.acc, JSValue.Number(0).just())) + 0x50.toByte() -> JumpIf(item.opUnits[1].toInt(), BiExp.Eq(LoadReg.acc, JSValue.Number(0).just())) + 0x51.toByte() -> JumpIf(item.opUnits[1].toInt(), BiExp.NEq(LoadReg.acc, JSValue.Number(0).just())) + in (0x52.toByte() .. 0x5f.toByte()) -> Disabled + 0x60.toByte() -> Assign(LoadReg.ACC, regId(item.opUnits[1]).ld()) + 0x61.toByte() -> Assign(regId(item.opUnits[1]), LoadReg.acc) + 0x62.toByte() -> Assign(LoadReg.ACC, JustImm(JSValue.Number(item.opUnits[1]))) + 0x63.toByte() -> Assign(LoadReg.ACC, JustImm(JSValue.Number(Double.fromBits(item.opUnits[1] as Long)))) + 0x64.toByte() -> Return.ReturnAcc + 0x65.toByte() -> Return.ReturnUndefined + + 0x6a.toByte() -> JSValue.Nan.just().st2Acc() + 0x6b.toByte() -> JSValue.Infinity.just().st2Acc() + + 0x6d.toByte() -> LoadReg(FunSimCtx.RegId.GLOBAL).st2Acc() + 0x6e.toByte() -> Disabled + 0x6f.toByte() -> LoadReg(FunSimCtx.RegId.THIS).st2Acc() + 0x70.toByte() -> JSValue.Hole.just().st2Acc() + + 0x74.toByte() -> JSValue.Function( + (item.ins.format[2] as InstFmt.MId).getMethod(item), + item.opUnits[3].toUnsignedInt() + ).just().st2Acc() + 0x75.toByte() -> NewClass( + JSValue.Function((item.ins.format[2] as InstFmt.MId).getMethod(item), item.opUnits[4].toUnsignedInt()), + (item.ins.format[3] as InstFmt.LId).getLA(item), + regId(item.opUnits[5]) + ).st2Acc() + 0x76.toByte() -> UaExp.GetTemplateObject(LoadReg.acc).st2Acc() + 0x77.toByte() -> AssignObj(ObjField.Name(LoadReg.ACC, JSValue.PROTO), regId(item.opUnits[2]).ld()) + 0x78.toByte() -> AssignObj(ObjField.Value(regId(item.opUnits[2]), regId(item.opUnits[3])), LoadReg.acc) + 0x79.toByte() -> AssignObj( + ObjField.Index(regId(item.opUnits[2]),item.opUnits[3].toUnsignedInt()), + LoadReg.acc + ) + 0x7a.toByte() -> AssignObj( + ObjField.Name(regId(item.opUnits[3]),(item.ins.format[2] as InstFmt.SId).getString(item)), + LoadReg.acc + ) + 0x7b.toByte() -> GetModuleNamespace(item.asm.code.method.clazz.getClass()!!.moduleInfo!!.moduleRequests[item.opUnits[1].toUnsignedInt()]).st2Acc() + + 0x7e.toByte() -> LoadExternalModule(item.asm.code.method.clazz.getClass()!!.moduleInfo!!.regularImports[item.opUnits[1].toUnsignedInt()]).st2Acc() + + 0x80.toByte() -> JSValue.ArrInst(emptyList()).just().st2Acc() + 0x81.toByte() -> JSValue.asArr(item.asm,(item.ins.format[2] as InstFmt.LId).getLA(item)).just().st2Acc() + 0x82.toByte() -> JSValue.asObj(item.asm,(item.ins.format[2] as InstFmt.LId).getLA(item)).just().st2Acc() + 0x83.toByte() -> { + val clazz = item.opUnits[3].toUnsignedInt() + val argC = item.opUnits[2].toUnsignedInt() + val args = if(argC.toUnsignedInt() > 1) ((clazz + 1) until (clazz + argC)) else emptyList() + Assign(FunSimCtx.RegId.ACC, NewInst(regId(clazz), args.map { regId(it) })) + } + 0x84.toByte() -> UaExp.TypeOf(LoadReg.acc).st2Acc() + 0x85.toByte() -> Assign(LoadReg.ACC,ObjField.Value(regId(item.opUnits[2]), LoadReg.ACC)) + 0x86.toByte() -> AssignObj(ObjField.Value(regId(item.opUnits[2]), regId(item.opUnits[3])), LoadReg.acc) + + 0x88.toByte() -> Assign(LoadReg.ACC, ObjField.Index(LoadReg.ACC,item.opUnits[2].toUnsignedInt())) + 0x89.toByte() -> AssignObj(ObjField.Index(regId(item.opUnits[2]),item.opUnits[3].toUnsignedInt()),LoadReg.acc) + + 0x8f.toByte() -> Assign(regId(item.opUnits[1]), regId(item.opUnits[2]).ld()) + + 0x98.toByte() -> Jump(item.opUnits[1].toInt()) + + 0x9a.toByte() -> JumpIf(item.opUnits[1].toInt(), BiExp.Eq(LoadReg.acc, JSValue.Number(0).just())) + 0x9b.toByte() -> JumpIf(item.opUnits[1].toInt(), BiExp.NEq(LoadReg.acc, JSValue.Number(0).just())) + 0x9c.toByte() -> JumpIf(item.opUnits[1].toInt(), BiExp.NEq(LoadReg.acc, JSValue.Number(0).just())) + in (0x9d.toByte() .. 0xaa.toByte()) -> Disabled + + 0xad.toByte() -> JSValue.Symbol.SymbolObj.just().st2Acc() + + 0xb0.toByte() -> Debugger + + 0xbd.toByte() -> DynamicImport.st2Acc() + + 0xc1.toByte() -> UaExp.GetTemplateObject(LoadReg.acc).st2Acc() + + 0xc7.toByte() -> AssignObj(ObjField.Name(LoadReg.ACC, JSValue.PROTO), regId(item.opUnits[2]).ld()) + 0xc8.toByte() -> AssignObj(ObjField.Value(regId(item.opUnits[2]), regId(item.opUnits[3])), LoadReg.acc) + + 0xd5.toByte() -> NOP + + 0xdb.toByte() -> AssignObj( + ObjField.Name(regId(item.opUnits[3]),(item.ins.format[2] as InstFmt.SId).getString(item)), + LoadReg.acc + ) + 0xdc.toByte() -> AssignObj( + ObjField.Name(regId(item.opUnits[3]),(item.ins.format[2] as InstFmt.SId).getString(item)), + LoadReg.acc + ) + + else -> UnImplemented(item) + } + } + } + + class UnImplemented(val item: AsmItem): Operation + + class JustAnno(val anno: String): Operation + + object NOP: Operation + object Debugger: Operation + object Disabled: Operation //指令功能未使能,暂不可用。 + object Deprecated: Operation + class NewLex(val size:Int): Operation + + + sealed interface Statement: Operation, FunSimCtx.Effect + class Assign(val target: FunSimCtx.RegId, val newValue: Expression): Statement { + override fun effected(): Sequence { + return newValue.effected() + target + } + + override fun read(): Sequence = newValue.read() + } + class AssignObj(val target: ObjField, val newValue: Expression): Statement + class Jump(val offset: Int): Statement + class JumpIf(val offset: Int,val condition: Expression): Statement + class Return private constructor(val hasValue: Boolean): Statement{ + companion object{ + val ReturnUndefined = Return(false) + val ReturnAcc = Return(true) + } + } + sealed interface Throw: Statement { + object Acc: Throw + class Error(val type: String,val msg: String = ""): Throw + } + + sealed interface Expression: FunSimCtx.Effect + class JustImm(val value: JSValue): Expression + class LoadReg(val regId: FunSimCtx.RegId): Expression { + override fun read(): Sequence = sequenceOf(regId) + companion object{ + val ACC = FunSimCtx.RegId.ACC + val acc = FunSimCtx.RegId.ACC.ld() + } + } + object DynamicImport: Expression { + override fun read(): Sequence = sequenceOf(LoadReg.ACC) + } + class NewClass( + val constructor: JSValue.Function, + val fields: LiteralArray, + val parent: FunSimCtx.RegId + ): Expression { + override fun read(): Sequence = sequenceOf(parent) + } + class NewInst(val clazz: FunSimCtx.RegId, val constructorArgs: List): Expression { + override fun read(): Sequence { + return constructorArgs.asSequence() + clazz + } + } + class CallAcc(val args: List = emptyList(), val overrideThis: FunSimCtx.RegId? = null): + Expression { + override fun read(): Sequence = args.asSequence() + FunSimCtx.RegId.ACC + override fun effected(): Sequence = sequenceOf(FunSimCtx.RegId.ACC) + } + class LoadExternalModule(val ext: ModuleLiteralArray.RegularImport): Expression { + override fun effected(): Sequence = sequenceOf(FunSimCtx.RegId.ACC) + } + class GetModuleNamespace(val ns: OhmUrl) : Expression { + override fun effected(): Sequence = sequenceOf(FunSimCtx.RegId.ACC) + } + + /** + * 一元表达式 + */ + sealed class UaExp( + val source: Expression + ): Expression { + override fun read(): Sequence = source.read() + override fun effected(): Sequence = source.effected() + + class TypeOf(source: Expression) : UaExp(source) + class ToNumber(source: Expression) : UaExp(source) + class ToNumeric(source: Expression) : UaExp(source) + class Neg(source: Expression) : UaExp(source) + class Not(source: Expression) : UaExp(source) + class Inc(source: Expression) : UaExp(source) + class Dec(source: Expression) : UaExp(source) + class IsTrue(source: Expression) : UaExp(source) + class IsFalse(source: Expression) : UaExp(source) + + class GetTemplateObject(source: Expression) : UaExp(source) + + } + + /** + * 二元表达式 + */ + sealed class BiExp( + val l: Expression, + val r: Expression, + ): Expression { + override fun read(): Sequence = l.read() + r.read() + override fun effected(): Sequence = l.effected() + r.effected() + class Add(l: Expression, r: Expression) : BiExp(l, r) + class Sub(l: Expression, r: Expression) : BiExp(l, r) + class Mul(l: Expression, r: Expression) : BiExp(l, r) + class Div(l: Expression, r: Expression) : BiExp(l, r) + class Mod(l: Expression, r: Expression) : BiExp(l, r) + class Eq(l: Expression, r: Expression) : BiExp(l, r) + class NEq(l: Expression, r: Expression) : BiExp(l, r) + class Less(l: Expression, r: Expression) : BiExp(l, r) + class LEq(l: Expression, r: Expression) : BiExp(l, r) + class Ge(l: Expression, r: Expression) : BiExp(l, r) + class GEq(l: Expression, r: Expression) : BiExp(l, r) + class Shl(l: Expression, r: Expression) : BiExp(l, r) + class Shr(l: Expression, r: Expression) : BiExp(l, r) + class AShr(l: Expression, r: Expression) : BiExp(l, r) + class And(l: Expression, r: Expression) : BiExp(l, r) + class Or(l: Expression, r: Expression) : BiExp(l, r) + class Xor(l: Expression, r: Expression) : BiExp(l, r) + class Exp(l: Expression, r: Expression) : BiExp(l, r) + + class IsIn(l: Expression, r: Expression) : BiExp(l, r) + class InstOf(l: Expression, r: Expression) : BiExp(l, r) + class StrictNEq(l: Expression, r: Expression) : BiExp(l, r) + class StrictEq(l: Expression, r: Expression) : BiExp(l, r) + } + + sealed class ObjField(val obj: FunSimCtx.RegId): Expression { + class Name(obj: FunSimCtx.RegId, val name: String): ObjField(obj) + class Index(obj: FunSimCtx.RegId, val index: Int): ObjField(obj) + class Value(obj: FunSimCtx.RegId, val value: FunSimCtx.RegId): ObjField(obj) + } + +} \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/suger.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/suger.kt new file mode 100644 index 0000000..518d485 --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/decompiler/behaviour/suger.kt @@ -0,0 +1,8 @@ +package me.yricky.oh.abcd.decompiler.behaviour + +import me.yricky.oh.abcd.isa.Inst.Companion.toUnsignedInt + +fun Operation.Expression.st2Acc(): Operation = Operation.Assign(FunSimCtx.RegId.ACC, this) +fun JSValue.just(): Operation.Expression = Operation.JustImm(this) +fun FunSimCtx.RegId.ld(): Operation.Expression = Operation.LoadReg(this) +fun regId(opUnit: Number): FunSimCtx.RegId = FunSimCtx.RegId.regId(opUnit.toUnsignedInt()) \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/isa/Asm.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/isa/Asm.kt index 52a3534..43724d8 100644 --- a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/isa/Asm.kt +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/abcd/isa/Asm.kt @@ -3,6 +3,7 @@ package me.yricky.oh.abcd.isa import me.yricky.oh.abcd.cfm.AbcMethod import me.yricky.oh.abcd.code.Code import me.yricky.oh.abcd.code.TryBlock +import me.yricky.oh.abcd.decompiler.behaviour.Operation import me.yricky.oh.abcd.isa.Inst.Companion.toUnsignedInt import me.yricky.oh.abcd.isa.util.BaseInstParser import me.yricky.oh.abcd.isa.util.InstCommentParser @@ -43,6 +44,10 @@ class Asm( li } + val operationList by lazy { + list.map { Operation.from(it) } + } + /** * 字节码中单个指令的汇编对象 @@ -62,6 +67,7 @@ class Asm( val next:AsmItem? get() = asm.list.getOrNull(index + 1) val nextOffset:Int get() = next?.codeOffset ?: asm.code.codeSize + val operation get() = asm.operationList[index] val prefix: Byte? get() = if(ins.format.firstOrNull() is InstFmt.Prefix) opUnits[0] as Byte else null diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/utils/LruMap.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/utils/LruMap.kt new file mode 100644 index 0000000..1ed53b8 --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/utils/LruMap.kt @@ -0,0 +1,7 @@ +package me.yricky.oh.utils + +class LruMap: java.util.LinkedHashMap() { + override fun removeEldestEntry(eldest: MutableMap.MutableEntry?): Boolean { + return (size > 10000) + } +} \ No newline at end of file diff --git a/modules/abcde/src/commonMain/kotlin/me/yricky/oh/utils/WeakLazy.kt b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/utils/WeakLazy.kt new file mode 100644 index 0000000..b6b8373 --- /dev/null +++ b/modules/abcde/src/commonMain/kotlin/me/yricky/oh/utils/WeakLazy.kt @@ -0,0 +1,24 @@ +package me.yricky.oh.utils + +import java.lang.ref.WeakReference +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class WeakLazy(private val initializer: () -> T) : ReadOnlyProperty { + private var weakRef = WeakReference(null) + private val lock = Any() + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + var instance = weakRef.get() + if (instance == null) { + synchronized(lock) { + instance = weakRef.get() + if (instance == null) { + instance = initializer() + weakRef = WeakReference(instance) + } + } + } + return instance!! + } +} \ No newline at end of file diff --git a/modules/abcde/src/jvmTest/java/me/yricky/oh/abcd/decompiler/CodeSegmentTest.kt b/modules/abcde/src/jvmTest/java/me/yricky/oh/abcd/decompiler/CodeSegmentTest.kt new file mode 100644 index 0000000..ade1bab --- /dev/null +++ b/modules/abcde/src/jvmTest/java/me/yricky/oh/abcd/decompiler/CodeSegmentTest.kt @@ -0,0 +1,80 @@ +package me.yricky.oh.abcd.decompiler + +import me.yricky.oh.abcd.AbcBuf +import me.yricky.oh.abcd.cfm.AbcClass +import me.yricky.oh.common.wrapAsLEByteBuf +import org.junit.Test +import java.io.File +import java.io.OutputStreamWriter +import java.nio.ByteOrder +import java.nio.channels.FileChannel + +class CodeSegmentTest{ + @Test + fun testGenLinear(){ + println(System.getenv("abcPath")) + val file = File(System.getenv("abcPath")) + val mmap = FileChannel.open(file.toPath()).map(FileChannel.MapMode.READ_ONLY,0,file.length()) + val abc = AbcBuf("", wrapAsLEByteBuf(mmap.order(ByteOrder.LITTLE_ENDIAN))) + var totalCount = 0 +// var passCount = 0 + abc.classes.asSequence().mapNotNull { it.value as? AbcClass } + .mapNotNull { it.methods.find { it.name == "func_main_0" } } + .mapNotNull { it.codeItem } + .forEach { + totalCount++ + if(it.tryBlocks.isEmpty()){ + CodeSegment.genLinear(it.asm) + } + } + } + + @Test + fun test(){ + println(System.getenv("abcPath")) + val file = File(System.getenv("abcPath")) + val mmap = FileChannel.open(file.toPath()).map(FileChannel.MapMode.READ_ONLY,0,file.length()) + val abc = AbcBuf("", wrapAsLEByteBuf(mmap.order(ByteOrder.LITTLE_ENDIAN))) + var totalCount = 0 + var passCount = 0 + var uIByteCodeCount = 0 + var othUnImplCount = 0 + var assertFailedCount = 0 + var lErrCount = 0 + + File(file.parentFile,"pass.txt").let { + println(it.absolutePath) + if(it.exists()){ + it.renameTo(File(file.parentFile,"pass.txt.old")) + } + } + val passFile = File(file.parentFile,"pass.txt") + val oldPassFile = File(file.parentFile,"pass.txt.old") + passFile.createNewFile() + val fos = OutputStreamWriter(passFile.outputStream()) + + abc.classes.asSequence().mapNotNull { it.value as? AbcClass } + .flatMap { it.methods } + .mapNotNull { it.codeItem } + .forEach { + totalCount++ + try { + ToJs(it.asm).toJS() + fos.append(it.method.name).append('\n') + passCount++ + } catch (_:ToJs.UnImplementedError){ + uIByteCodeCount++ + } catch (_:NotImplementedError){ + othUnImplCount++ + } catch (_:AssertionError) { + assertFailedCount++ + } catch (_:IllegalStateException) { + lErrCount++ + } +// if(it.tryBlocks.isEmpty()){ +// +// } + } + println("total:${totalCount},pass:${passCount},uIByteCodeCount:${uIByteCodeCount},othUnImplCount:${othUnImplCount},assertFailedCount:${assertFailedCount},lErrCount:${lErrCount}") + } +} \ No newline at end of file