//===-- DILParser.cpp -----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // This implements the recursive descent parser for the Data Inspection // Language (DIL), and its helper functions, which will eventually underlie the // 'frame variable' command. The language that this parser recognizes is // described in lldb/docs/dil-expr-lang.ebnf // //===----------------------------------------------------------------------===// #include "lldb/ValueObject/DILParser.h" #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Utility/DiagnosticsRendering.h" #include "lldb/ValueObject/DILAST.h" #include "lldb/ValueObject/DILEval.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FormatAdapters.h" #include #include #include #include #include namespace lldb_private::dil { DILDiagnosticError::DILDiagnosticError(llvm::StringRef expr, const std::string &message, uint32_t loc, uint16_t err_len) : ErrorInfo(make_error_code(std::errc::invalid_argument)) { DiagnosticDetail::SourceLocation sloc = { FileSpec{}, /*line=*/1, static_cast(loc + 1), err_len, false, /*in_user_input=*/true}; std::string rendered_msg = llvm::formatv(":1:{0}: {1}\n 1 | {2}\n | ^", loc + 1, message, expr); m_detail.source_location = sloc; m_detail.severity = lldb::eSeverityError; m_detail.message = message; m_detail.rendered = std::move(rendered_msg); } llvm::Expected DILParser::Parse(llvm::StringRef dil_input_expr, DILLexer lexer, std::shared_ptr frame_sp, lldb::DynamicValueType use_dynamic, bool use_synthetic, bool fragile_ivar, bool check_ptr_vs_member) { llvm::Error error = llvm::Error::success(); DILParser parser(dil_input_expr, lexer, frame_sp, use_dynamic, use_synthetic, fragile_ivar, check_ptr_vs_member, error); ASTNodeUP node_up = parser.Run(); if (error) return error; return node_up; } DILParser::DILParser(llvm::StringRef dil_input_expr, DILLexer lexer, std::shared_ptr frame_sp, lldb::DynamicValueType use_dynamic, bool use_synthetic, bool fragile_ivar, bool check_ptr_vs_member, llvm::Error &error) : m_ctx_scope(frame_sp), m_input_expr(dil_input_expr), m_dil_lexer(std::move(lexer)), m_error(error), m_use_dynamic(use_dynamic), m_use_synthetic(use_synthetic) {} ASTNodeUP DILParser::Run() { ASTNodeUP expr = ParseExpression(); Expect(Token::Kind::eof); return expr; } // Parse an expression. // // expression: // primary_expression // ASTNodeUP DILParser::ParseExpression() { return ParseUnaryExpression(); } // Parse an unary_expression. // // unary_expression: // unary_operator expression // primary_expression // // unary_operator: // "&" // "*" // ASTNodeUP DILParser::ParseUnaryExpression() { if (CurToken().IsOneOf({Token::amp, Token::star})) { Token token = CurToken(); uint32_t loc = token.GetLocation(); m_dil_lexer.Advance(); auto rhs = ParseExpression(); switch (token.GetKind()) { case Token::star: return std::make_unique(loc, UnaryOpKind::Deref, std::move(rhs)); case Token::amp: return std::make_unique(loc, UnaryOpKind::AddrOf, std::move(rhs)); default: llvm_unreachable("invalid token kind"); } } return ParsePrimaryExpression(); } // Parse a primary_expression. // // primary_expression: // id_expression // "(" expression ")" // ASTNodeUP DILParser::ParsePrimaryExpression() { if (CurToken().IsOneOf({Token::coloncolon, Token::identifier})) { // Save the source location for the diagnostics message. uint32_t loc = CurToken().GetLocation(); auto identifier = ParseIdExpression(); return std::make_unique(loc, identifier); } if (CurToken().Is(Token::l_paren)) { m_dil_lexer.Advance(); auto expr = ParseExpression(); Expect(Token::r_paren); m_dil_lexer.Advance(); return expr; } BailOut(llvm::formatv("Unexpected token: {0}", CurToken()), CurToken().GetLocation(), CurToken().GetSpelling().length()); return std::make_unique(); } // Parse nested_name_specifier. // // nested_name_specifier: // type_name "::" // namespace_name "::" // nested_name_specifier identifier "::" // std::string DILParser::ParseNestedNameSpecifier() { // The first token in nested_name_specifier is always an identifier, or // '(anonymous namespace)'. switch (CurToken().GetKind()) { case Token::l_paren: { // Anonymous namespaces need to be treated specially: They are // represented the the string '(anonymous namespace)', which has a // space in it (throwing off normal parsing) and is not actually // proper C++> Check to see if we're looking at // '(anonymous namespace)::...' // Look for all the pieces, in order: // l_paren 'anonymous' 'namespace' r_paren coloncolon if (m_dil_lexer.LookAhead(1).Is(Token::identifier) && (m_dil_lexer.LookAhead(1).GetSpelling() == "anonymous") && m_dil_lexer.LookAhead(2).Is(Token::identifier) && (m_dil_lexer.LookAhead(2).GetSpelling() == "namespace") && m_dil_lexer.LookAhead(3).Is(Token::r_paren) && m_dil_lexer.LookAhead(4).Is(Token::coloncolon)) { m_dil_lexer.Advance(4); assert( (CurToken().Is(Token::identifier) || CurToken().Is(Token::l_paren)) && "Expected an identifier or anonymous namespace, but not found."); // Continue parsing the nested_namespace_specifier. std::string identifier2 = ParseNestedNameSpecifier(); if (identifier2.empty()) { Expect(Token::identifier); identifier2 = CurToken().GetSpelling(); m_dil_lexer.Advance(); } return "(anonymous namespace)::" + identifier2; } return ""; } // end of special handling for '(anonymous namespace)' case Token::identifier: { // If the next token is scope ("::"), then this is indeed a // nested_name_specifier if (m_dil_lexer.LookAhead(1).Is(Token::coloncolon)) { // This nested_name_specifier is a single identifier. std::string identifier = CurToken().GetSpelling(); m_dil_lexer.Advance(1); Expect(Token::coloncolon); m_dil_lexer.Advance(); // Continue parsing the nested_name_specifier. return identifier + "::" + ParseNestedNameSpecifier(); } return ""; } default: return ""; } } // Parse an id_expression. // // id_expression: // unqualified_id // qualified_id // // qualified_id: // ["::"] [nested_name_specifier] unqualified_id // ["::"] identifier // // identifier: // ? Token::identifier ? // std::string DILParser::ParseIdExpression() { // Try parsing optional global scope operator. bool global_scope = false; if (CurToken().Is(Token::coloncolon)) { global_scope = true; m_dil_lexer.Advance(); } // Try parsing optional nested_name_specifier. std::string nested_name_specifier = ParseNestedNameSpecifier(); // If nested_name_specifier is present, then it's qualified_id production. // Follow the first production rule. if (!nested_name_specifier.empty()) { // Parse unqualified_id and construct a fully qualified id expression. auto unqualified_id = ParseUnqualifiedId(); return llvm::formatv("{0}{1}{2}", global_scope ? "::" : "", nested_name_specifier, unqualified_id); } // No nested_name_specifier, but with global scope -- this is also a // qualified_id production. Follow the second production rule. if (global_scope) { Expect(Token::identifier); std::string identifier = CurToken().GetSpelling(); m_dil_lexer.Advance(); return llvm::formatv("{0}{1}", global_scope ? "::" : "", identifier); } // This is unqualified_id production. return ParseUnqualifiedId(); } // Parse an unqualified_id. // // unqualified_id: // identifier // // identifier: // ? Token::identifier ? // std::string DILParser::ParseUnqualifiedId() { Expect(Token::identifier); std::string identifier = CurToken().GetSpelling(); m_dil_lexer.Advance(); return identifier; } void DILParser::BailOut(const std::string &error, uint32_t loc, uint16_t err_len) { if (m_error) // If error is already set, then the parser is in the "bail-out" mode. Don't // do anything and keep the original error. return; m_error = llvm::make_error(m_input_expr, error, loc, err_len); // Advance the lexer token index to the end of the lexed tokens vector. m_dil_lexer.ResetTokenIdx(m_dil_lexer.NumLexedTokens() - 1); } void DILParser::Expect(Token::Kind kind) { if (CurToken().IsNot(kind)) { BailOut(llvm::formatv("expected {0}, got: {1}", kind, CurToken()), CurToken().GetLocation(), CurToken().GetSpelling().length()); } } } // namespace lldb_private::dil