diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index c5a6830c7..d28a2c9a4 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -118,6 +118,7 @@ public class Compiler { getLog().append("INITIAL CONTROL FLOW GRAPH"); getLog().append(program.getGraph().toString(program)); + new Pass1AssertUsedVars(program).execute(); new Pass1ExtractInlineStrings(program).execute(); new Pass1EliminateUncalledProcedures(program).execute(); new Pass1EliminateUnusedVars(program).execute(); diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1AssertUsedVars.java b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertUsedVars.java new file mode 100644 index 000000000..badf38de0 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertUsedVars.java @@ -0,0 +1,78 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.model.*; + +import java.util.Collection; +import java.util.LinkedHashSet; + +/** + * Checks that all used variables are assigned a value before their use + */ +public class Pass1AssertUsedVars extends Pass1Base { + + public Pass1AssertUsedVars(Program program) { + super(program); + } + + @Override + public boolean executeStep() { + + new Pass3StatementIndices(getProgram()).generateStatementIndices(); + new Pass3VariableReferenceInfos(getProgram()).generateVariableReferenceInfos(); + VariableReferenceInfos referenceInfos = getProgram().getVariableReferenceInfos(); + + ControlFlowBlock beginBlock = getProgram().getGraph().getBlock(new LabelRef(SymbolRef.BEGIN_BLOCK_NAME)); + assertUsedVars(beginBlock, referenceInfos, new LinkedHashSet<>(), new LinkedHashSet<>()); + getProgram().setVariableReferenceInfos(null); + new Pass3StatementIndices(getProgram()).clearStatementIndices(); + + return false; + } + + + /** + * Assert that all used vars have been assigned values before the use. + * Follow the control flow of the graph recursively. + * @param block The block to examine + * @param referenceInfos Information about assigned/used variables in statements + * @param defined Variables already assigned a value at the point of the first execution of the block + * @param visited Blocks already visited + */ + public void assertUsedVars(ControlFlowBlock block, VariableReferenceInfos referenceInfos, Collection defined, Collection visited) { + if(visited.contains(block.getLabel())) { + return; + } + visited.add(block.getLabel()); + for (Statement statement : block.getStatements()) { + Collection used = referenceInfos.getUsed(statement); + for (VariableRef usedRef : used) { + if(!defined.contains(usedRef)) { + throw new CompileError("Error! Variable used before being defined "+usedRef.toString(getProgram())+" in "+statement.toString(getProgram(), false)); + } + } + Collection defd = referenceInfos.getDefined(statement); + for (VariableRef definedRef : defd) { + defined.add(definedRef); + } + if(statement instanceof StatementCall) { + StatementCall call = (StatementCall) statement; + Procedure procedure = getProgram().getScope().getProcedure(call.getProcedure()); + for (String paramName : procedure.getParameterNames()) { + defined.add(procedure.getVariable(paramName).getRef()); + } + ControlFlowBlock procedureStart = getProgram().getGraph().getBlock(call.getProcedure().getLabelRef()); + + assertUsedVars(procedureStart, referenceInfos, defined, visited); + } else if(statement instanceof StatementConditionalJump) { + StatementConditionalJump cond= (StatementConditionalJump) statement; + ControlFlowBlock jumpTo = getProgram().getGraph().getBlock(cond.getDestination()); + assertUsedVars(jumpTo, referenceInfos, defined, visited); + } + } + ControlFlowBlock successor = getProgram().getGraph().getBlock(block.getDefaultSuccessor()); + if(successor!=null) { + assertUsedVars(successor, referenceInfos, defined, visited); + } + } + +} diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1EliminateUnusedVars.java b/src/main/java/dk/camelot64/kickc/passes/Pass1EliminateUnusedVars.java index 486dcc362..59dcd12df 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass1EliminateUnusedVars.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1EliminateUnusedVars.java @@ -17,7 +17,6 @@ public class Pass1EliminateUnusedVars extends Pass1Base { public boolean executeStep() { new Pass3StatementIndices(getProgram()).generateStatementIndices(); new Pass3VariableReferenceInfos(getProgram()).generateVariableReferenceInfos(); - VariableReferenceInfos referenceInfos = getProgram().getVariableReferenceInfos(); boolean modified = false; @@ -51,7 +50,6 @@ public class Pass1EliminateUnusedVars extends Pass1Base { getProgram().setVariableReferenceInfos(null); new Pass3StatementIndices(getProgram()).clearStatementIndices(); - return modified; } diff --git a/src/main/java/dk/camelot64/kickc/test/TestErrors.java b/src/main/java/dk/camelot64/kickc/test/TestErrors.java index 0744a982b..113c29971 100644 --- a/src/main/java/dk/camelot64/kickc/test/TestErrors.java +++ b/src/main/java/dk/camelot64/kickc/test/TestErrors.java @@ -32,11 +32,6 @@ public class TestErrors extends TestCase { compileAndCompare("inline-asm-param"); } - public void testUseUninitialized() throws IOException, URISyntaxException { - String filename = "useuninitialized"; - compileAndCompare(filename); - } - public void testForRangeSymbolic() throws IOException, URISyntaxException { String filename = "forrangesymbolic"; compileAndCompare(filename); diff --git a/src/main/java/dk/camelot64/kickc/test/TestPrograms.java b/src/main/java/dk/camelot64/kickc/test/TestPrograms.java index 3d6f0c65e..cffa8149e 100644 --- a/src/main/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/main/java/dk/camelot64/kickc/test/TestPrograms.java @@ -287,6 +287,16 @@ public class TestPrograms extends TestCase { fail("Expected compile error."); } + public void testUseUninitialized() throws IOException, URISyntaxException { + try { + compileAndCompare("useuninitialized"); + } catch (CompileError e) { + // expecting error! + return; + } + fail("Expected compile error."); + } + public void testTypeMismatch() throws IOException, URISyntaxException { try { compileAndCompare("typemismatch"); diff --git a/src/main/java/dk/camelot64/kickc/test/useuninitialized.kc b/src/main/java/dk/camelot64/kickc/test/useuninitialized.kc index 71c713e9a..61c7c8696 100644 --- a/src/main/java/dk/camelot64/kickc/test/useuninitialized.kc +++ b/src/main/java/dk/camelot64/kickc/test/useuninitialized.kc @@ -1,7 +1,13 @@ // Use an uninitialized variable - should fail gracefully! // Currently it gets into the optimization phase, where the optimization runs into problems understanding the assignment/usage-graph and ends up failing on an exception +byte b; +byte s; +byte w=2; void main() { - byte b; - byte s=b+1; + s=b+1; + b=3; + byte* screen = $0400; + *screen = b; + *(screen+1) = s; } \ No newline at end of file