Added inspection to warn about unexpected condition code unaffecting instructions before conditional instructions.

Extended documentation.
Bugfix in M68kDeadWriteInspection.
This commit is contained in:
2021-08-05 18:42:32 +02:00
parent 2abb5af8b0
commit 1dcf288d27
7 changed files with 221 additions and 6 deletions
@@ -19,8 +19,8 @@ class M68kDeadWriteInspection : AbstractBaseM68kLocalInspectionTool() {
companion object {
private const val DISPLAY_NAME = "Dead writes to registers"
private const val DEAD_WRITE_MSG = "Register %s is overwritten later without being used"
private const val POSSIBLY_DEAD_WRITE_MSG = "Register %s is overwritten later (only CC evaluated?)"
private const val DEAD_WRITE_MSG_TEMPLATE = "Register %s is overwritten later without being used"
private const val POSSIBLY_DEAD_WRITE_MSG_TEMPLATE = "Register %s is overwritten later (only CC evaluated?)"
}
override fun getDisplayName() = DISPLAY_NAME
@@ -69,7 +69,7 @@ class M68kDeadWriteInspection : AbstractBaseM68kLocalInspectionTool() {
val currAsmInstruction = PsiTreeUtil.getChildOfType(currStatement, M68kAsmInstruction::class.java) ?: continue
val (isaData, currAdrMode) = findExactIsaDataAndAllowedAdrModeForInstruction(currAsmInstruction) ?: continue
if (isaData.changesControlFlow) break
val testedCc = getConcreteTestedCcFromMnemonic(currAsmInstruction.asmOp.mnemonic, isaData, adrMode)
val testedCc = getConcreteTestedCcFromMnemonic(currAsmInstruction.asmOp.mnemonic, isaData, currAdrMode)
if (((testedCc and ccModification) > 0) && !ccOverwritten) ccTested = true
if (currAdrMode.affectedCc != 0) ccOverwritten = true
if (checkIfInstructionUsesRegister(currAsmInstruction, register)) {
@@ -89,7 +89,7 @@ class M68kDeadWriteInspection : AbstractBaseM68kLocalInspectionTool() {
manager.createProblemDescriptor(
asmInstruction,
asmInstruction,
(if (ccTested) POSSIBLY_DEAD_WRITE_MSG else DEAD_WRITE_MSG).format(register.regname),
(if (ccTested) POSSIBLY_DEAD_WRITE_MSG_TEMPLATE else DEAD_WRITE_MSG_TEMPLATE).format(register.regname),
if (ccTested) ProblemHighlightType.WEAK_WARNING else ProblemHighlightType.WARNING,
isOnTheFly
)
@@ -0,0 +1,54 @@
package de.platon42.intellij.plugins.m68k.inspections
import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.m68k.asm.M68kIsa.findMatchingInstructions
import de.platon42.intellij.plugins.m68k.psi.*
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.findExactIsaDataAndAllowedAdrModeForInstruction
class M68kUnexpectedConditionalInstructionInspection : AbstractBaseM68kLocalInspectionTool() {
companion object {
private const val DISPLAY_NAME = "Unaffected condition codes before conditional instruction"
private const val UNAFFECTED_CONDITION_CODES_MSG_TEMPLATE = "Condition codes unaffected by instruction (%s - %s)"
}
override fun getDisplayName() = DISPLAY_NAME
override fun checkAsmInstruction(asmInstruction: M68kAsmInstruction, manager: InspectionManager, isOnTheFly: Boolean): Array<ProblemDescriptor>? {
val asmOp = asmInstruction.asmOp
if (asmInstruction.addressingModeList.isEmpty()) return emptyArray()
val isaDataCandidates = findMatchingInstructions(asmOp.mnemonic)
if (isaDataCandidates.isEmpty()) return emptyArray()
val (isaData, adrMode) = findExactIsaDataAndAllowedAdrModeForInstruction(asmInstruction) ?: return emptyArray()
if ((adrMode.affectedCc > 0) || (adrMode.testedCc > 0) || isaData.changesControlFlow) return emptyArray()
var currStatement = asmInstruction.parent as M68kStatement
while (true) {
currStatement = PsiTreeUtil.getNextSiblingOfType(currStatement, M68kStatement::class.java) ?: break
val globalLabel = PsiTreeUtil.findChildOfType(currStatement, M68kGlobalLabel::class.java)
if (globalLabel != null) break
if (PsiTreeUtil.findChildOfAnyType(currStatement, M68kMacroCall::class.java, M68kPreprocessorDirective::class.java) != null) break
val currAsmInstruction = PsiTreeUtil.getChildOfType(currStatement, M68kAsmInstruction::class.java) ?: continue
val (currIsaData, currAdrMode) = findExactIsaDataAndAllowedAdrModeForInstruction(currAsmInstruction) ?: break
val testedCc = M68kIsaUtil.getConcreteTestedCcFromMnemonic(currAsmInstruction.asmOp.mnemonic, currIsaData, currAdrMode)
if (testedCc == 0) break
return arrayOf(
manager.createProblemDescriptor(
asmInstruction,
asmInstruction,
UNAFFECTED_CONDITION_CODES_MSG_TEMPLATE.format(isaData.mnemonic, isaData.description),
ProblemHighlightType.WARNING,
isOnTheFly
)
)
}
return emptyArray()
}
}
+3
View File
@@ -53,6 +53,9 @@
<localInspection implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kDeadWriteInspection"
displayName="Dead writes to registers" groupName="M68k"
enabledByDefault="true" level="WARNING"/>
<localInspection implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kUnexpectedConditionalInstructionInspection"
displayName="Unaffected condition codes before conditional instruction" groupName="M68k"
enabledByDefault="true" level="WARNING"/>
</extensions>
<actions>
@@ -0,0 +1,18 @@
<html>
<body>
Usually, it is expected that an instruction checking the condition codes or using
the condition codes is preceded by an instruction that actually affects the condition
codes. This inspection checks that this is the case.
For example, the 'movea' and 'adda' instructions (which can be (and are!) often written
as 'move' and 'add') do not affect the condition codes and a conditional branch
will not work as expected.
However, this does not need to be a programming error. Advanced coders sometimes
make use of the fact that instructions do not change condition codes and thus
optimize the order of execution.
<!-- tooltip end -->
Analysis is terminated at the next global label, macrocall or preprocessor statement.
</body>
</html>
@@ -0,0 +1,74 @@
package de.platon42.intellij.plugins.m68k.inspections
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
internal class M68kUnexpectedConditionalInstructionInspectionTest : AbstractInspectionTest() {
@Test
internal fun movea_causes_warning_when_used_for_conditional_branching(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kUnexpectedConditionalInstructionInspection::class.java)
myFixture.configureByText(
"unexpectedcc.asm", """
move.l d0,a1
bne.s .cont
rts
.cont
"""
)
assertHighlightings(myFixture, 1, "Condition codes unaffected by instruction (movea - Move Address)")
}
@Test
internal fun no_warning_on_consecutive_conditional_branches(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kUnexpectedConditionalInstructionInspection::class.java)
myFixture.configureByText(
"unexpectedcc.asm", """
move.b P61_arplist(pc,d0),d0
beq.b .arp0
bmi.b .arp1
"""
)
assertThat(myFixture.doHighlighting()).isEmpty()
}
@Test
internal fun no_warning_on_macro_call_inbetween(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kUnexpectedConditionalInstructionInspection::class.java)
myFixture.configureByText(
"unexpectedcc.asm", """
move.l pd_PalCurShamPalPtr(a4),a1
PALSTEPDOWN
bne.s .loopline
"""
)
assertThat(myFixture.doHighlighting()).isEmpty()
}
@Test
internal fun no_warning_flow_control_instruction(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kUnexpectedConditionalInstructionInspection::class.java)
myFixture.configureByText(
"unexpectedcc.asm", """
bsr foo
bne.s .loopline
"""
)
assertThat(myFixture.doHighlighting()).isEmpty()
}
@Test
internal fun warning_on_conditional_set_series_with_suba(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kUnexpectedConditionalInstructionInspection::class.java)
myFixture.configureByText(
"unexpectedcc.asm", """
sub.l a0,a0
seq d0
sne d1
"""
)
assertHighlightings(myFixture, 1, "Condition codes unaffected by instruction (suba - Subtract Address)")
}
}