]> git.tdb.fi Git - libs/gl.git/commitdiff
Make the GLSL parser resilient against common errors
authorMikko Rasa <tdb@tdb.fi>
Sun, 28 Feb 2021 00:12:48 +0000 (02:12 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sun, 28 Feb 2021 11:41:54 +0000 (13:41 +0200)
It will now attempt to continue parsing the source, with the intent of
reporting all syntax errors at once.

source/glsl/glsl_error.h
source/glsl/parser.cpp
source/glsl/parser.h
source/glsl/tokenizer.cpp
source/glsl/tokenizer.h

index 46ca49be35daf98062a9cff156426fde9882eb6b..1b005e0fc9db96c4230a9268fc8cc54e7eedf1f5 100644 (file)
@@ -13,6 +13,7 @@ struct Location;
 class invalid_shader_source: public std::runtime_error
 {
 public:
+       invalid_shader_source(const std::string &w): runtime_error(w) { }
        invalid_shader_source(const Location &, const std::string &);
 #if __cplusplus>=201103L
        template<typename... Args>
index 5ffc2a9b023f284b2837990e06f7a8533955d5fc..d9fdbc4ea998e56d98d2209e6a5d79558511bf34 100644 (file)
@@ -1,3 +1,4 @@
+#include <msp/core/raii.h>
 #include <msp/strings/format.h>
 #include <msp/strings/regex.h>
 #include <msp/strings/utils.h>
@@ -57,8 +58,13 @@ void Parser::parse_source(const string &name, unsigned index)
        source_index = index;
        source_reference(1, name);
        tokenizer.begin(name, source);
-       while(RefPtr<Statement> statement = parse_global_declaration())
-               cur_stage->content.body.push_back(statement);
+       allow_stage_change = true;
+       while(!tokenizer.peek_token().empty())
+               if(RefPtr<Statement> statement = parse_with_recovery(&Parser::parse_global_declaration))
+                       cur_stage->content.body.push_back(statement);
+
+       if(!errors.empty())
+               throw invalid_shader_source(join(errors.begin(), errors.end(), "\n"));
 }
 
 void Parser::set_required_version(const Version &ver)
@@ -181,11 +187,54 @@ bool Parser::is_identifier(const string &token)
        return re.match(token);
 }
 
+template<typename T>
+RefPtr<T> Parser::parse_with_recovery(RefPtr<T> (Parser::*parse_func)())
+{
+       tokenizer.clear_progress_mark();
+       try
+       {
+               return (this->*parse_func)();
+       }
+       catch(const invalid_shader_source &exc)
+       {
+               errors.push_back(exc.what());
+       }
+
+       if(tokenizer.get_last_token()!=";" || !tokenizer.get_progress_mark())
+       {
+               unsigned scope_level = 0;
+               while(1)
+               {
+                       if(tokenizer.peek_token()=="}" && scope_level==0)
+                       {
+                               if(!tokenizer.get_progress_mark())
+                                       tokenizer.parse_token();
+                               break;
+                       }
+
+                       string token = tokenizer.parse_token();
+                       if(token=="}")
+                       {
+                               --scope_level;
+                               if(scope_level==0)
+                                       break;
+                       }
+                       else if(token=="{")
+                               ++scope_level;
+                       else if(token==";" && scope_level==0)
+                               break;
+                       else if(token.empty())
+                               break;
+               }
+       }
+
+       return RefPtr<T>();
+}
+
 RefPtr<Statement> Parser::parse_global_declaration()
 {
-       allow_stage_change = true;
        string token = tokenizer.peek_token();
-       allow_stage_change = false;
+       SetFlag disallow(allow_stage_change, false);
 
        if(token=="import")
                return parse_import();
@@ -262,6 +311,11 @@ RefPtr<Statement> Parser::parse_statement()
        }
        else if(is_qualifier(token) || is_type(token))
                return parse_variable_declaration();
+       else if(token==";")
+       {
+               tokenizer.parse_token();
+               throw invalid_shader_source(tokenizer.get_location(), "Empty statement not allowed");
+       }
        else if(!token.empty())
        {
                RefPtr<ExpressionStatement> expr = new ExpressionStatement;
@@ -357,7 +411,8 @@ void Parser::parse_block(Block &block, bool require_braces, RefPtr<T> (Parser::*
        if(have_braces)
        {
                while(tokenizer.peek_token()!="}")
-                       block.body.push_back((this->*parse_content)());
+                       if(RefPtr<Statement> node = parse_with_recovery(parse_content))
+                               block.body.push_back(node);
        }
        else
                block.body.push_back((this->*parse_content)());
index 9ab1232aaa0d9ad7aec82b24ee873b8b4e998e16..acfb7d9eed9a8cc857becf5436e59a92bac522ba 100644 (file)
@@ -24,6 +24,7 @@ private:
        Module *module;
        Stage *cur_stage;
        std::set<std::string> declared_types;
+       std::vector<std::string> errors;
 
 public:
        Parser();
@@ -60,6 +61,8 @@ private:
        void preprocess_stage();
 
        RefPtr<Statement> parse_global_declaration();
+       template<typename T>
+       RefPtr<T> parse_with_recovery(RefPtr<T> (Parser::*)());
        RefPtr<Statement> parse_statement();
        RefPtr<Import> parse_import();
        RefPtr<Precision> parse_precision();
index b7efb8f8f9414d759c3cce3843593e14ae2a72e5..6ba139ff9fd92b37a459fc619e79088902d2bea2 100644 (file)
@@ -34,11 +34,13 @@ const string &Tokenizer::peek_token(unsigned index)
 {
        while(next_tokens.size()<=index)
                next_tokens.push_back(parse_token_());
-       return (last_token = next_tokens[index]);
+       return next_tokens[index];
 }
 
 const string &Tokenizer::parse_token()
 {
+       progress_mark = true;
+
        if(!next_tokens.empty())
        {
                last_token = next_tokens.front();
index 20ea756a9099ea192368f60750f791e0238624c3..46aa62fbea282c61ae016dd232f39b6fa73971f9 100644 (file)
@@ -29,6 +29,7 @@ private:
        std::string::const_iterator iter;
        std::string::const_iterator source_end;
        Location location;
+       bool progress_mark;
        bool allow_preprocess;
        bool suppress_line_advance;
        std::string last_token;
@@ -40,8 +41,11 @@ public:
        void begin(const std::string &, const std::string &);
        const std::string &peek_token(unsigned = 0);
        const std::string &parse_token();
+       const std::string &get_last_token() const { return last_token; }
        void expect(const std::string &);
        void set_location(const Location &);
+       void clear_progress_mark() { progress_mark = false; }
+       bool get_progress_mark() const { return progress_mark; }
        const Location &get_location() const { return location; }
 private:
        std::string parse_token_();