Skip to content

Commit

Permalink
Inline single nested else statement & inverse else comparator inspect…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
GregHib committed Mar 18, 2019
1 parent a6dea83 commit 85ef117
Show file tree
Hide file tree
Showing 13 changed files with 474 additions and 9 deletions.
13 changes: 11 additions & 2 deletions deobfuscation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group 'world.gregs.intellij.plugins'
version '1.0.1'
version '1.1.0'

repositories {
mavenCentral()
Expand All @@ -20,11 +20,20 @@ compileKotlin {
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}

processResources {
from('src/inspectionDescriptions/') {
into 'inspectionDescriptions'
}
}

intellij {
version '2018.3.5'
}
patchPluginXml {
changeNotes """
Stopped reversing comparator if expressions are flipped.<br>
Inspections added:<br>
Inverse else comparator<br>
Inline nested else statments<br>
"""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports when an else statement has a single if statement block which can be inlined, example 'else { if(i == 0) {'<br>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports when any else if statements are used with a reversible expression, example 'if(i != 71) { } else { }'<br>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package world.gregs.intellij.plugins

import com.intellij.psi.PsiExpression
import com.siyeh.ig.psiutils.JavaPsiMathUtil

object DeobfuscateUtil {
fun isNumber(expression: PsiExpression): Boolean {
return JavaPsiMathUtil.getNumberFromLiteral(expression) != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.siyeh.ig.psiutils.CommentTracker
import com.siyeh.ig.psiutils.JavaPsiMathUtil
import org.jetbrains.annotations.NonNls
import world.gregs.intellij.plugins.DeobfuscateToolBundle
import world.gregs.intellij.plugins.DeobfuscateUtil.isNumber

class PointlessBitwiseComparatorInspection : BaseInspection() {

Expand Down Expand Up @@ -154,10 +155,6 @@ class PointlessBitwiseComparatorInspection : BaseInspection() {
return expression != null && (expression.type == PsiType.INT && expression.text == "0xffffffff" || expression.type == PsiType.LONG && expression.text == "0xffffffffffffffffL")
}

private fun isNumber(expression: PsiExpression): Boolean {
return JavaPsiMathUtil.getNumberFromLiteral(expression) != null
}

private fun isBitwiseOperator(expression: PsiExpression?): Boolean {
val express = PsiUtil.skipParenthesizedExprDown(expression) as? PsiBinaryExpression ?: return false
val sign = express.operationTokenType
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package world.gregs.intellij.plugins.flow

import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair
import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtilCore
import com.siyeh.ig.BaseInspectionVisitor
import com.siyeh.ig.InspectionGadgetsFix
import com.siyeh.ig.psiutils.CommentTracker
import com.siyeh.ig.style.ControlFlowStatementVisitorBase
import com.siyeh.ig.style.SingleStatementInBlockInspection
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NotNull
import world.gregs.intellij.plugins.DeobfuscateToolBundle

class InlineNestedElseInspection : SingleStatementInBlockInspection() {

override fun getDisplayName(): String {
return DeobfuscateToolBundle.message("inline.nested.else.display.name")
}

public override fun buildErrorString(vararg infos: Any): String {
return DeobfuscateToolBundle.message("inline.nested.else.problem.descriptor")
}

override fun isEnabledByDefault(): Boolean {
return true
}

override fun buildVisitor(): BaseInspectionVisitor {
return object : ControlFlowStatementVisitorBase() {
override fun isApplicable(body: PsiStatement?): Boolean {
//If body is else branch of parent
val parent = body?.parent
if(body is PsiBlockStatement && parent is PsiIfStatement && parent.elseBranch == body) {
val codeBlock = body.codeBlock
if (PsiUtilCore.hasErrorElementChild(codeBlock)) {
return false
}

//Else block must only contain a single if statement
val statement = codeBlock.statements.firstOrNull()
if (codeBlock.statementCount == 1 && statement is PsiIfStatement) {
if (PsiUtilCore.hasErrorElementChild(statement)) {
return false
}
return true
}
}
return false
}

override fun getOmittedBodyBounds(body: PsiStatement?): Pair<PsiElement, PsiElement>? {
if (body is PsiBlockStatement) {
val codeBlock = body.codeBlock
if (codeBlock.statementCount == 1) {
val statement = codeBlock.statements.firstOrNull()
if (statement?.textContains('\n') == true) {
return Pair(statement, statement)
}
}
}
return null
}

}
}

override fun buildFix(vararg infos: Any?): InspectionGadgetsFix? {
return object : InspectionGadgetsFix() {
@Nls
@NotNull
override fun getName(): String {
return DeobfuscateToolBundle.message("inline.nested.else.quickfix")
}

@Nls
@NotNull
override fun getFamilyName(): String {
return DeobfuscateToolBundle.message("inline.nested.else.family.quickfix")
}

override fun doFix(project: Project, descriptor: ProblemDescriptor) {
var statement = PsiTreeUtil.getNonStrictParentOfType(descriptor.startElement, PsiStatement::class.java)
?: return

if (statement is PsiBlockStatement) {
statement = PsiTreeUtil.getNonStrictParentOfType(statement.parent, PsiStatement::class.java)
?: return
}

val body = when (statement) {
is PsiLoopStatement -> statement.body
is PsiIfStatement -> statement.elseBranch
else -> null
} as? PsiBlockStatement ?: return

val child = body.codeBlock.statements.firstOrNull()
if (body.codeBlock.statementCount != 1 || child == null) {
return
}

val ct = CommentTracker()
val text = ct.text(child)
val replacementExp = ct.replace(body, text)
CodeStyleManager.getInstance(project).reformat(replacementExp)
ct.insertCommentsBefore(statement)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package world.gregs.intellij.plugins.flow

import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.JavaTokenType.*
import com.intellij.util.containers.ContainerUtil
import com.siyeh.ig.BaseInspection
import com.siyeh.ig.BaseInspectionVisitor
import com.siyeh.ig.InspectionGadgetsFix
import com.siyeh.ig.PsiReplacementUtil
import com.siyeh.ig.psiutils.CommentTracker
import com.siyeh.ig.psiutils.ComparisonUtils
import org.jetbrains.annotations.NonNls
import world.gregs.intellij.plugins.DeobfuscateToolBundle
import world.gregs.intellij.plugins.DeobfuscateUtil.isNumber

class InverseElseComparatorInspection : BaseInspection() {

override fun getDisplayName(): String {
return DeobfuscateToolBundle.message("inverse.else.comparator.display.name")
}

public override fun buildErrorString(vararg infos: Any): String {
val expression = infos[0] as PsiExpression
val statement = infos[1] as PsiIfStatement
val ct = CommentTracker()
val replacement = calculateReplacementExpression(statement, ct)
return DeobfuscateToolBundle.message(
"comparator.can.be.inverted.problem.descriptor",
ct.text(expression),
replacement
)
}

override fun isEnabledByDefault(): Boolean {
return true
}

override fun buildVisitor(): BaseInspectionVisitor {
return object : BaseInspectionVisitor() {
override fun visitIfStatement(statement: PsiIfStatement) {
super.visitIfStatement(statement)

//Must have else block statement
if(statement.elseBranch !is PsiBlockStatement) {
return
}

val expression = statement.condition as? PsiBinaryExpression
?: return

//Check expression has operands
val left = expression.lOperand
val right = expression.rOperand
?: return

//Check format matches expected
val sign = expression.operationTokenType
val invalid = when (sign) {
//Variable doesn't equal number or visa versa
NE -> left is PsiReferenceExpression && !isNumber(right) || isNumber(left) && right !is PsiReferenceExpression
//Both operands are binary expressions that have a range operation type
OROR -> left !is PsiBinaryExpression || right !is PsiBinaryExpression || !comparisonTokens.contains(left.operationTokenType) || !comparisonTokens.contains(right.operationTokenType)
//Both operands are binary expressions that both have a not-equal operators
ANDAND -> left !is PsiBinaryExpression || right !is PsiBinaryExpression || left.operationTokenType != NE || right.operationTokenType != NE
else -> true
}

//If format invalid; ignore
if (invalid) {
return
}

registerError(statement, expression, statement)
}
}
}

public override fun buildFix(vararg infos: Any): InspectionGadgetsFix? {
return object : InspectionGadgetsFix() {

override fun getFamilyName(): String {
return DeobfuscateToolBundle.message("inverse.else.comparator.invert.quickfix")
}

public override fun doFix(project: Project, descriptor: ProblemDescriptor) {
val statement = descriptor.psiElement as PsiIfStatement

val expression = statement.condition as? PsiBinaryExpression
?: return

val ct = CommentTracker()

//Replace expression with negative comparator
val expected = calculateReplacementExpression(statement, ct)
PsiReplacementUtil.replaceExpression(expression, expected, ct)

//Switch branches
val branch = statement.thenBranch
val elseBranch = statement.elseBranch?.copy() as? PsiStatement
if(branch != null) {
statement.setElseBranch(branch)
}
if(elseBranch != null) {
statement.setThenBranch(elseBranch)
}
}
}
}

@NonNls
internal fun calculateReplacementExpression(statement: PsiIfStatement, ct: CommentTracker): String {
val expression = statement.condition as? PsiBinaryExpression
?: return ""

val sign = expression.operationTokenType
return when (sign) {
NE -> replaceOperation(expression, ComparisonUtils.getNegatedComparison(sign), ct)
OROR, ANDAND -> {
//Get operands as expressions
val left = expression.lOperand as? PsiBinaryExpression
?: return ""
val right = expression.rOperand as? PsiBinaryExpression
?: return ""
//Replace expressions with negated operators
val leftNew = replaceOperation(left, ComparisonUtils.getNegatedComparison(left.operationTokenType), ct)
val rightNew = replaceOperation(right, ComparisonUtils.getNegatedComparison(right.operationTokenType), ct)
//Change statement operator
val opposite = when (sign) {
OROR -> "&&"
ANDAND -> "||"
else -> throw IllegalStateException()
}
"$leftNew $opposite $rightNew"
}
else -> ""
}
}

/**
* Replaces [expression] operator
* @param expression The binary expression who's operator to replace
* @param operator The new operator
* @param ct CommentTracker
* @return [expression] as a string with the new [operator]
*/
private fun replaceOperation(expression: PsiBinaryExpression, operator: String, ct: CommentTracker): String {
return "${ct.text(expression.lOperand)} $operator ${ct.text(expression.rOperand!!)}"
}

companion object {
internal val comparisonTokens = ContainerUtil.immutableSet(LT, GT, LE, GE)
}
}
14 changes: 12 additions & 2 deletions deobfuscation/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<idea-plugin>
<id>world.gregs.intellij.plugins.deobfuscation</id>
<name>Java Bitwise Deobsfuscator</name>
<name>Greg's Deobsfuscation Tools</name>
<vendor email="greg@gregs.world" url="http://gregs.world">Greg</vendor>

<description><![CDATA[
Highlights pointless bitwise comparator obfuscation to be simplified<br>
A few intellij plugins to assist refactoring obfuscated code<br>
]]></description>

<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
Expand All @@ -19,6 +19,16 @@
groupKey="group.names.bitwise.operation.issues" enabledByDefault="true" level="WARNING"
hasStaticDescription="true"
implementationClass="world.gregs.intellij.plugins.bitwise.PointlessBitwiseComparatorInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="InverseElseComparator" bundle="world.gregs.intellij.plugins.DeobfuscateToolBundle"
key="inverse.else.comparator.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.control.flow.issues" enabledByDefault="true" level="WARNING"
hasStaticDescription="true"
implementationClass="world.gregs.intellij.plugins.flow.InverseElseComparatorInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="InlineNestedElse" bundle="world.gregs.intellij.plugins.DeobfuscateToolBundle"
key="inline.nested.else.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.control.flow.issues" enabledByDefault="true" level="WARNING"
hasStaticDescription="true"
implementationClass="world.gregs.intellij.plugins.flow.InlineNestedElseInspection"/>
</extensions>

<actions>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
pointless.bitwise.comparator.display.name=Pointless bitwise comparator
expression.can.be.replaced.problem.descriptor=<code>#ref</code> can be replaced with ''{0}'' #loc
pointless.bitwise.comparator.simplify.quickfix=Simplify
pointless.bitwise.comparator.simplify.quickfix=Simplify

inverse.else.comparator.display.name=Inverse if else comparator
comparator.can.be.inverted.problem.descriptor=''{0}'' can be inverted to ''{1}'' #loc
inverse.else.comparator.invert.quickfix=Invert

inline.nested.else.display.name=Inline nested else statement
inline.nested.else.problem.descriptor=<code>#ref</code> can be inlined #loc
inline.nested.else.quickfix=Inline 'else' statement
inline.nested.else.family.quickfix=Inline statements

0 comments on commit 85ef117

Please sign in to comment.