diff --git a/README.md b/README.md
index 61fb362..97aa60d 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,70 @@ good enough" to get started, and I can return to demo coding with its current st
- Structure view
- Documentation provider for symbol definitions and mnemonics (listing available addressing modes etc.).
+### Inspections
+
+#### M68kSyntaxInspection - Assembly instruction validity
+
+Checks the validity of the current instruction. If an instruction is not recognized, you may get one of the following errors:
+
+- Instruction _mnemonic_ not supported on selected cpu (you won't get this currently as only MC68000 is supported)
+- No operands expected for _mnemonic_
+- Second operand _op_ unexpected for _mnemonic_
+- Unsupported addressing mode for _mnemonic_
+- Unsupported addressing mode _op_ for first operand of _mnemonic_
+- Unsupported addressing mode _op_ for second operand of _mnemonic_
+- Unsupported addressing modes for operands in this order for _mnemonic_ (try swapping)
+- Instruction _mnemonic_ is unsized (you tried to specify `.b`, `.w` or `.l`)
+- Operation size _(.b,.w,.l)_ unsupported for _mnemonic_
+- Operation size _(.b,.w,.l)_ unsupported (should be _(.b,.w,.l)_)
+
+#### M68kDeadWriteInspection - Dead writes to registers
+
+This inspection looks at register writes and tries to find instructions that renders a write moot because it was overwritten by another instruction before
+anything useful was done with it.
+
+Analysis is aborted at global labels, flow control instructions, directives
+(e.g. conditional assembly) and macros with the register names as parameter.
+
+The inspection tries to take condition code changing into account and puts out a weak warning if the statement merely changes condition codes before the
+contents of the register are overwritten. In this case, it is sometimes better to replace `move` by `tst`.
+
+#### M68kUnexpectedConditionalInstructionInspection - Unaffected condition codes before conditional instruction
+
+Especially for novice coders, it is not clear that some instructions do not affect the condition codes for a subsequent condition branch or `scc` instruction.
+`movea`, `adda` and `suba` come to my mind.
+
+The inspection will report such suspicious instruction sequences.
+
+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.
+
+### Documentation provider
+
+#### M68kSymbolDefinitionDocumentationProvider
+
+Provides the assigned value of a `=`, `set` or `equ` symbol definition when hovering over a symbol.
+
+#### M68kRegisterFlowDocumentationProvider
+
+When hovering over or placing the cursor at a data or address register, the documentation will scan through the instructions backwards and forwards and will
+show all read, changes of the register contents. It does this until an instruction is found that defines (sets) the contents of the register
+(according to the size of the instruction where the cursor was placed).
+
+The analysis ignores all code flow instructions and might be inaccurate for branches, macro use, etc. It also stops at global labels.
+
+The documentation will search up to 100 instructions in each direction, but only four when hovering over the register
+(so if you need the whole analysis, use the documentation window).
+
+#### M68kInstructionDocumentationProvider
+
+When hovering over a mnemonic, it will show a short description of the assembly instruction.
+
+For the documentation window, affected condition codes, allowed operation sizes and addressing modes are listed for the selected instruction
+(so only stuff from `cmpa` is listed when you're looking at a `cmp.w a0,a1` instruction).
+
+If the current statement has no valid syntax, the instruction details of all matching mnemonics will be shown instead.
+
## Known issues
- `Find Usages` always shows _"Unclassified"_ though it shouldn't (?)
@@ -80,7 +144,8 @@ make it work with JUnit 5. Feel free to use the code (in package ```de.platon42.
- Performance: Optimized mnemonic lookup.
- Enhancement: Reworked Instruction Documentation provider, now shows condition codes.
- Bugfix: In ISA `exg` is no longer treated as setting a definitive value.
-- New: Added inspection find dead writes to registers!
+- New: Added inspection to find dead writes to registers!
+- New: Added inspection to warn about unexpected condition code unaffecting instructions before conditional instructions.
### V0.4 (03-Aug-21)
diff --git a/build.gradle b/build.gradle
index fb37bc7..2554c72 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,7 +66,8 @@ patchPluginXml {
Performance: Optimized mnemonic lookup.
Enhancement: Reworked Instruction Documentation provider, now shows condition codes.
Bugfix: In ISA exg is no longer treated as setting a definitive value.
- New: Added inspection find dead writes to registers!
+ New: Added inspection to find dead writes to registers!
+ New: Added inspection to warn about unexpected condition code unaffecting instructions before conditional instructions.
V0.4 (03-Aug-21)
diff --git a/src/main/java/de/platon42/intellij/plugins/m68k/inspections/M68kDeadWriteInspection.kt b/src/main/java/de/platon42/intellij/plugins/m68k/inspections/M68kDeadWriteInspection.kt
index 971cd51..9c2f860 100644
--- a/src/main/java/de/platon42/intellij/plugins/m68k/inspections/M68kDeadWriteInspection.kt
+++ b/src/main/java/de/platon42/intellij/plugins/m68k/inspections/M68kDeadWriteInspection.kt
@@ -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
)
diff --git a/src/main/java/de/platon42/intellij/plugins/m68k/inspections/M68kUnexpectedConditionalInstructionInspection.kt b/src/main/java/de/platon42/intellij/plugins/m68k/inspections/M68kUnexpectedConditionalInstructionInspection.kt
new file mode 100644
index 0000000..dd8b329
--- /dev/null
+++ b/src/main/java/de/platon42/intellij/plugins/m68k/inspections/M68kUnexpectedConditionalInstructionInspection.kt
@@ -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? {
+ 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()
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 327a41c..1a1ee5a 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -53,6 +53,9 @@
+
diff --git a/src/main/resources/inspectionDescriptions/M68kUnexpectedConditionalInstruction.html b/src/main/resources/inspectionDescriptions/M68kUnexpectedConditionalInstruction.html
new file mode 100644
index 0000000..83c1e10
--- /dev/null
+++ b/src/main/resources/inspectionDescriptions/M68kUnexpectedConditionalInstruction.html
@@ -0,0 +1,18 @@
+
+
+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.
+
+
+Analysis is terminated at the next global label, macrocall or preprocessor statement.
+
+
\ No newline at end of file
diff --git a/src/test/java/de/platon42/intellij/plugins/m68k/inspections/M68kUnexpectedConditionalInstructionInspectionTest.kt b/src/test/java/de/platon42/intellij/plugins/m68k/inspections/M68kUnexpectedConditionalInstructionInspectionTest.kt
new file mode 100644
index 0000000..3cd519d
--- /dev/null
+++ b/src/test/java/de/platon42/intellij/plugins/m68k/inspections/M68kUnexpectedConditionalInstructionInspectionTest.kt
@@ -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)")
+ }
+}
\ No newline at end of file