From 6681787288a9da8fa5d413c713073a8f18015a87 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 26 Sep 2018 21:21:33 +0200 Subject: [PATCH] fix handling of (too) large integer literals, and range check crash --- compiler/examples/mandelbrot.p8 | 4 +- compiler/src/prog8/ast/AST.kt | 61 +++++++++++++------ compiler/src/prog8/ast/AstChecker.kt | 23 ++++--- compiler/src/prog8/compiler/Compiler.kt | 2 +- .../prog8/optimizing/ConstExprEvaluator.kt | 3 +- 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/compiler/examples/mandelbrot.p8 b/compiler/examples/mandelbrot.p8 index 763734a4c..7e86c6fbc 100644 --- a/compiler/examples/mandelbrot.p8 +++ b/compiler/examples/mandelbrot.p8 @@ -24,10 +24,10 @@ _vm_gfx_text(5, 5, 7, "Calculating Mandelbrot Fractal...") for pixely in yoffset to yoffset+height-1 { - yy = flt((pixely-yoffset))/height/3.6+0.4 + yy = flt((pixely-yoffset))/height/3.6+0.4 ; @todo why is /height/3.6 not const-folded??? for pixelx in xoffset to xoffset+width-1 { - xx = flt((pixelx-xoffset))/width/3+0.2 + xx = flt((pixelx-xoffset))/width/3+0.2 ; @todo why is /width/3 not const-folded??? x = 0.0 y = 0.0 diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index b8d5ab5bb..cb9bce8d3 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -729,7 +729,7 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I val leftDt = left.resultingDatatype(namespace) val rightDt = right.resultingDatatype(namespace) return when(operator) { - "+", "-", "*", "**", "/", "%" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt) + "+", "-", "*", "**", "%" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt) "//" -> if(leftDt==null || rightDt==null) null else integerDivisionOpDt(leftDt, rightDt) "&" -> leftDt "|" -> leftDt @@ -738,6 +738,28 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I "<", ">", "<=", ">=", "==", "!=" -> DataType.BYTE + "/" -> { + val rightNum = right.constValue(namespace)?.asNumericValue?.toDouble() + if(rightNum!=null) { + when(leftDt) { + DataType.BYTE -> + when(rightDt) { + DataType.BYTE -> DataType.BYTE + DataType.WORD -> if(rightNum <= -256 || rightNum >= 256) DataType.BYTE else DataType.WORD + DataType.FLOAT -> if(rightNum <= -256 || rightNum >= 256) DataType.BYTE else DataType.FLOAT + else -> throw FatalAstException("invalid rightDt $rightDt") + } + DataType.WORD -> + when(rightDt) { + DataType.BYTE, DataType.WORD -> DataType.WORD + DataType.FLOAT -> if(rightNum <= -65536 || rightNum >= 65536) DataType.WORD else DataType.FLOAT + else -> throw FatalAstException("invalid rightDt $rightDt") + } + DataType.FLOAT -> DataType.FLOAT + else -> throw FatalAstException("invalid leftDt $leftDt") + } + } else if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt) + } else -> throw FatalAstException("resulting datatype check for invalid operator $operator") } } @@ -784,10 +806,8 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I } } -private data class ByteOrWordLiteral(val intvalue: Int, val datatype: DataType) { - fun asWord() = ByteOrWordLiteral(intvalue, DataType.WORD) - fun asByte() = ByteOrWordLiteral(intvalue, DataType.BYTE) -} +private data class NumericLiteral(val number: Number, val datatype: DataType) + class LiteralValue(val type: DataType, val bytevalue: Short? = null, @@ -835,7 +855,8 @@ class LiteralValue(val type: DataType, return when (value) { // note: we cheat a little here and allow negative integers during expression evaluations in -128..255 -> LiteralValue(DataType.BYTE, bytevalue = value.toShort(), position = position) - else -> LiteralValue(DataType.WORD, wordvalue = value.toInt(), position = position) + in -32768..65535 -> LiteralValue(DataType.WORD, wordvalue = value.toInt(), position = position) + else -> throw FatalAstException("integer overflow: $value") } } } @@ -1412,7 +1433,7 @@ private fun prog8Parser.ModulestatementContext.toAst() : IStatement { private fun prog8Parser.BlockContext.toAst() : IStatement = - Block(identifier().text, integerliteral()?.toAst()?.intvalue, statement_block().toAst(), toPosition()) + Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), toPosition()) private fun prog8Parser.Statement_blockContext.toAst(): MutableList = @@ -1552,7 +1573,7 @@ private fun prog8Parser.ReturnstmtContext.toAst() : IStatement { private fun prog8Parser.UnconditionaljumpContext.toAst(): IStatement { - val address = integerliteral()?.toAst()?.intvalue + val address = integerliteral()?.toAst()?.number?.toInt() val identifier = if(identifier()!=null) identifier()?.toAst() else scoped_identifier()?.toAst() @@ -1569,7 +1590,7 @@ private fun prog8Parser.SubroutineContext.toAst() : Subroutine { return Subroutine(identifier().text, if(sub_params() ==null) emptyList() else sub_params().toAst(), if(sub_returns() ==null) emptyList() else sub_returns().toAst(), - sub_address()?.integerliteral()?.toAst()?.intvalue, + sub_address()?.integerliteral()?.toAst()?.number?.toInt(), if(statement_block() ==null) mutableListOf() else statement_block().toAst(), toPosition()) } @@ -1614,17 +1635,20 @@ private fun prog8Parser.DirectiveContext.toAst() : Directive = private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg = - DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.intvalue, toPosition()) + DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition()) -private fun prog8Parser.IntegerliteralContext.toAst(): ByteOrWordLiteral { - fun makeLiteral(text: String, radix: Int, forceWord: Boolean): ByteOrWordLiteral { +private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral { + fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral { val integer: Int var datatype = DataType.BYTE if(radix==10) { integer = text.toInt() - if(integer in 256..65535) - datatype = DataType.WORD + datatype = when(integer) { + in 0..255 -> DataType.BYTE + in 256..65535 -> DataType.WORD + else -> DataType.FLOAT + } } else if(radix==2) { if(text.length>8) datatype = DataType.WORD @@ -1636,7 +1660,7 @@ private fun prog8Parser.IntegerliteralContext.toAst(): ByteOrWordLiteral { } else { throw FatalAstException("invalid radix") } - return ByteOrWordLiteral(integer, if(forceWord) DataType.WORD else datatype) + return NumericLiteral(integer, if(forceWord) DataType.WORD else datatype) } val terminal: TerminalNode = children[0] as TerminalNode val integerPart = this.intpart.text @@ -1661,9 +1685,10 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression { val intLit = litval.integerliteral()?.toAst() when { intLit!=null -> when(intLit.datatype) { - DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.intvalue.toShort(), position = litval.toPosition()) - DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.intvalue, position = litval.toPosition()) - else -> throw FatalAstException("invalid datatype for integer literal") + DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) + DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) + DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue= intLit.number.toDouble(), position = litval.toPosition()) + else -> throw FatalAstException("invalid datatype for numeric literal") } litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition()) litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = litval.stringliteral().text, position = litval.toPosition()) diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 232af7ee2..64306ed6d 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -267,8 +267,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: val targetDatatype = assignment.target.determineDatatype(namespace, assignment) val constVal = assignment.value.constValue(namespace) if(constVal!=null) { - checkValueTypeAndRange(targetDatatype, null, assignment.value as LiteralValue) - // todo: fix crash here when assignment value is a functioncall sounch as round() + checkValueTypeAndRange(targetDatatype, null, constVal) } else { val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace) if(sourceDatatype==null) { @@ -587,17 +586,17 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } DataType.BYTE -> { val number = value.asIntegerValue ?: return if (value.floatvalue!=null) - err("unsigned byte integer value expected instead of float; possible loss of precision") + err("unsigned byte value expected instead of float; possible loss of precision") else - err("unsigned byte integer value expected") + err("unsigned byte value expected") if (number < 0 || number > 255) return err("value '$number' out of range for unsigned byte") } DataType.WORD -> { val number = value.asIntegerValue ?: return if (value.floatvalue!=null) - err("unsigned word integer value expected instead of float; possible loss of precision") + err("unsigned word value expected instead of float; possible loss of precision") else - err("unsigned word integer value expected") + err("unsigned word value expected") if (number < 0 || number > 65535) return err("value '$number' out of range for unsigned word") } @@ -638,9 +637,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: return err("initialization value must be an array of bytes") } else { val number = value.bytevalue ?: return if (value.floatvalue!=null) - err("unsigned byte integer value expected instead of float; possible loss of precision") + err("unsigned byte value expected instead of float; possible loss of precision") else - err("unsigned byte integer value expected") + err("unsigned byte value expected") if (number < 0 || number > 255) return err("value '$number' out of range for unsigned byte") } @@ -671,9 +670,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } } else { val number = value.asIntegerValue ?: return if (value.floatvalue!=null) - err("unsigned byte or word integer value expected instead of float; possible loss of precision") + err("unsigned byte or word value expected instead of float; possible loss of precision") else - err("unsigned byte or word integer value expected") + err("unsigned byte or word value expected") if (number < 0 || number > 65535) return err("value '$number' out of range for unsigned word") } @@ -698,7 +697,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } } else { val number = value.bytevalue - ?: return err("unsigned byte integer value expected") + ?: return err("unsigned byte value expected") if (number < 0 || number > 255) return err("value '$number' out of range for unsigned byte") } @@ -734,7 +733,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: if(sourceDatatype==DataType.WORD && targetDatatype==DataType.BYTE) checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position)) else if(sourceDatatype==DataType.FLOAT && (targetDatatype==DataType.BYTE || targetDatatype==DataType.WORD)) - checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.toString().toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)) + checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.toString().toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to byte/word arithmetic", position)) else checkResult.add(ExpressionError("cannot assign ${sourceDatatype.toString().toLowerCase()} to ${targetDatatype.toString().toLowerCase()}", position)) diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 123d0f81f..996d5728f 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -294,7 +294,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva val rightDt = right.resultingDatatype(namespace) if (leftDt == DataType.BYTE || leftDt == DataType.WORD) { if(rightDt==DataType.FLOAT) - printWarning("byte or word value implicitly converted to float. Suggestion: use explicit flt() conversion or revert to integer arithmetic", left.position) + printWarning("byte or word value implicitly converted to float. Suggestion: use explicit flt() conversion or revert to byte/word arithmetic", left.position) } } diff --git a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt index f8ec205d6..fdcdc8010 100644 --- a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt +++ b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt @@ -208,7 +208,8 @@ class ConstExprEvaluator { } - private fun divideByZeroError(pos: Position): Unit = throw ExpressionError("division by zero", pos) + private fun divideByZeroError(pos: Position): Unit = + throw ExpressionError("division by zero", pos) private fun divide(left: LiteralValue, right: LiteralValue): LiteralValue {