Fix overflows when tokenizing integer literals
This commit is contained in:
committed by
Chris Fallin
parent
825258939b
commit
6a523938de
@@ -3,7 +3,8 @@
|
|||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
fuzz_target!(|s: &str| {
|
fuzz_target!(|s: &str| {
|
||||||
let lexer = isle::lexer::Lexer::from_str(s, "fuzz-input.isle");
|
if let Ok(lexer) = isle::lexer::Lexer::from_str(s, "fuzz-input.isle") {
|
||||||
let mut parser = isle::parser::Parser::new(lexer);
|
let mut parser = isle::parser::Parser::new(lexer);
|
||||||
let _ = parser.parse_defs();
|
let _ = parser.parse_defs();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Lexer for the ISLE language.
|
//! Lexer for the ISLE language.
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result, Source};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -72,7 +72,7 @@ pub enum Token {
|
|||||||
|
|
||||||
impl<'a> Lexer<'a> {
|
impl<'a> Lexer<'a> {
|
||||||
/// Create a new lexer for the given source contents and filename.
|
/// Create a new lexer for the given source contents and filename.
|
||||||
pub fn from_str(s: &'a str, filename: &'a str) -> Lexer<'a> {
|
pub fn from_str(s: &'a str, filename: &'a str) -> Result<Lexer<'a>> {
|
||||||
let mut l = Lexer {
|
let mut l = Lexer {
|
||||||
filenames: vec![filename.into()],
|
filenames: vec![filename.into()],
|
||||||
file_texts: vec![s.into()],
|
file_texts: vec![s.into()],
|
||||||
@@ -86,8 +86,8 @@ impl<'a> Lexer<'a> {
|
|||||||
},
|
},
|
||||||
lookahead: None,
|
lookahead: None,
|
||||||
};
|
};
|
||||||
l.reload();
|
l.reload()?;
|
||||||
l
|
Ok(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new lexer from the given files.
|
/// Create a new lexer from the given files.
|
||||||
@@ -131,7 +131,7 @@ impl<'a> Lexer<'a> {
|
|||||||
},
|
},
|
||||||
lookahead: None,
|
lookahead: None,
|
||||||
};
|
};
|
||||||
l.reload();
|
l.reload()?;
|
||||||
Ok(l)
|
Ok(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +162,18 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_token(&mut self) -> Option<(Pos, Token)> {
|
fn error(&self, pos: Pos, msg: impl Into<String>) -> Error {
|
||||||
|
Error::ParseError {
|
||||||
|
msg: msg.into(),
|
||||||
|
src: Source::new(
|
||||||
|
self.filenames[pos.file].clone(),
|
||||||
|
self.file_texts[pos.file].clone(),
|
||||||
|
),
|
||||||
|
span: miette::SourceSpan::from((pos.offset, 1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_token(&mut self) -> Result<Option<(Pos, Token)>> {
|
||||||
fn is_sym_first_char(c: u8) -> bool {
|
fn is_sym_first_char(c: u8) -> bool {
|
||||||
match c {
|
match c {
|
||||||
b'-' | b'0'..=b'9' | b'(' | b')' | b';' => false,
|
b'-' | b'0'..=b'9' | b'(' | b')' | b';' => false,
|
||||||
@@ -194,26 +205,26 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.pos.offset == self.buf.len() {
|
if self.pos.offset == self.buf.len() {
|
||||||
return None;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let char_pos = self.pos;
|
let char_pos = self.pos;
|
||||||
match self.buf[self.pos.offset] {
|
match self.buf[self.pos.offset] {
|
||||||
b'(' => {
|
b'(' => {
|
||||||
self.advance_pos();
|
self.advance_pos();
|
||||||
Some((char_pos, Token::LParen))
|
Ok(Some((char_pos, Token::LParen)))
|
||||||
}
|
}
|
||||||
b')' => {
|
b')' => {
|
||||||
self.advance_pos();
|
self.advance_pos();
|
||||||
Some((char_pos, Token::RParen))
|
Ok(Some((char_pos, Token::RParen)))
|
||||||
}
|
}
|
||||||
b'@' => {
|
b'@' => {
|
||||||
self.advance_pos();
|
self.advance_pos();
|
||||||
Some((char_pos, Token::At))
|
Ok(Some((char_pos, Token::At)))
|
||||||
}
|
}
|
||||||
b'<' => {
|
b'<' => {
|
||||||
self.advance_pos();
|
self.advance_pos();
|
||||||
Some((char_pos, Token::Lt))
|
Ok(Some((char_pos, Token::Lt)))
|
||||||
}
|
}
|
||||||
c if is_sym_first_char(c) => {
|
c if is_sym_first_char(c) => {
|
||||||
let start = self.pos.offset;
|
let start = self.pos.offset;
|
||||||
@@ -226,7 +237,7 @@ impl<'a> Lexer<'a> {
|
|||||||
let end = self.pos.offset;
|
let end = self.pos.offset;
|
||||||
let s = std::str::from_utf8(&self.buf[start..end])
|
let s = std::str::from_utf8(&self.buf[start..end])
|
||||||
.expect("Only ASCII characters, should be UTF-8");
|
.expect("Only ASCII characters, should be UTF-8");
|
||||||
Some((start_pos, Token::Symbol(s.to_string())))
|
Ok(Some((start_pos, Token::Symbol(s.to_string()))))
|
||||||
}
|
}
|
||||||
c if (c >= b'0' && c <= b'9') || c == b'-' => {
|
c if (c >= b'0' && c <= b'9') || c == b'-' => {
|
||||||
let start_pos = self.pos;
|
let start_pos = self.pos;
|
||||||
@@ -236,11 +247,16 @@ impl<'a> Lexer<'a> {
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
let mut num = 0;
|
let mut num = 0_i64;
|
||||||
while self.pos.offset < self.buf.len()
|
while self.pos.offset < self.buf.len()
|
||||||
&& (self.buf[self.pos.offset] >= b'0' && self.buf[self.pos.offset] <= b'9')
|
&& (self.buf[self.pos.offset] >= b'0' && self.buf[self.pos.offset] <= b'9')
|
||||||
{
|
{
|
||||||
num = (num * 10) + (self.buf[self.pos.offset] - b'0') as i64;
|
let base = num
|
||||||
|
.checked_mul(10)
|
||||||
|
.ok_or_else(|| self.error(start_pos, "integer literal too large"))?;
|
||||||
|
num = base
|
||||||
|
.checked_add((self.buf[self.pos.offset] - b'0') as i64)
|
||||||
|
.ok_or_else(|| self.error(start_pos, "integer literal too large"))?;
|
||||||
self.advance_pos();
|
self.advance_pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,16 +265,24 @@ impl<'a> Lexer<'a> {
|
|||||||
} else {
|
} else {
|
||||||
Token::Int(num)
|
Token::Int(num)
|
||||||
};
|
};
|
||||||
Some((start_pos, tok))
|
Ok(Some((start_pos, tok)))
|
||||||
}
|
}
|
||||||
c => panic!("Unexpected character '{}' at offset {}", c, self.pos.offset),
|
c => panic!("Unexpected character '{}' at offset {}", c, self.pos.offset),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload(&mut self) {
|
/// Get the next token from this lexer's token stream, if any.
|
||||||
|
pub fn next(&mut self) -> Result<Option<(Pos, Token)>> {
|
||||||
|
let tok = self.lookahead.take();
|
||||||
|
self.reload()?;
|
||||||
|
Ok(tok)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload(&mut self) -> Result<()> {
|
||||||
if self.lookahead.is_none() && self.pos.offset < self.buf.len() {
|
if self.lookahead.is_none() && self.pos.offset < self.buf.len() {
|
||||||
self.lookahead = self.next_token();
|
self.lookahead = self.next_token()?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peek ahead at the next token.
|
/// Peek ahead at the next token.
|
||||||
@@ -272,16 +296,6 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::iter::Iterator for Lexer<'a> {
|
|
||||||
type Item = (Pos, Token);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<(Pos, Token)> {
|
|
||||||
let tok = self.lookahead.take();
|
|
||||||
self.reload();
|
|
||||||
tok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
/// Is this an `Int` token?
|
/// Is this an `Int` token?
|
||||||
pub fn is_int(&self) -> bool {
|
pub fn is_int(&self) -> bool {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl<'a> Parser<'a> {
|
|||||||
if !f(peek) {
|
if !f(peek) {
|
||||||
return Err(self.error(pos, format!("Unexpected token {:?}", peek)));
|
return Err(self.error(pos, format!("Unexpected token {:?}", peek)));
|
||||||
}
|
}
|
||||||
Ok(self.lexer.next().unwrap().1)
|
Ok(self.lexer.next()?.unwrap().1)
|
||||||
} else {
|
} else {
|
||||||
Err(self.error(self.lexer.pos(), "Unexpected EOF".to_string()))
|
Err(self.error(self.lexer.pos(), "Unexpected EOF".to_string()))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user