# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: false require 'ffi' # require the gem require 'contrast-agent-lib' require 'contrast/utils/object_share' module Contrast module AgentLib # This module is defined in Rust as external, we used it here. # Initializes the AgentLib. Here will be all methods from # the C bindings contrast_c::panic_error module. module Panic extend FFI::Library ffi_lib ContrastAgentLib::CONTRAST_C attach_function :last_error_message, %i[pointer int32 pointer int32], :int32 attach_function :last_error_message_length, [], :int32 attach_function :last_error_stack_length, [], :int32 private # Returns the last Error message saved for the thread. # If there is no message it will return empty string. # # @return [String] Empty string if no errors, or a # message + stack trace concat String. def dl__get_error message_length = last_error_message_length stack_length = last_error_stack_length return Contrast::Utils::ObjectShare::EMPTY_STRING if message_length.zero? # If the buffer size is retrieved as 0 we need to add 1 byte for it # else the Agent-Lib check for allocated memory would fail. stack_length += 1 if stack_length.zero? message_buffer = FFI::MemoryPointer.new(:char, message_length) stack_buffer = FFI::MemoryPointer.new(:char, stack_length) return dl__parse_error(message_buffer, stack_buffer) if last_error_message(message_buffer, message_length, stack_buffer, stack_length).positive? Contrast::Utils::ObjectShare::EMPTY_STRING ensure # The free methods are auto called form the FFI::MemoryPointer # when the variable is out of scope. Making sure it's all free, # and call with safe navigation if is been already cleaned. message_buffer&.free stack_buffer&.free end # Retrieves the buffer bytecode and transforms it into String. # # @param buffer [FFI::MemoryPointer, nil] of type char (String) # @return [String] the received message. def dl__read_buffer buffer return Contrast::Utils::ObjectShare::EMPTY_STRING unless buffer string = buffer.read_string return string unless string.empty? # If there is a incorrect buffer size or the message can't be read # from the FFI::MemoryPointer#read_string method we can still try # and get the partial message: bytes = buffer.get_array_of_int8(0, buffer.size)&.map { |byte| byte.zero? ? nil : byte } bytes.compact.pack('C*').force_encoding('UTF-8') if bytes&.cs__is_a?(Array) end # The stack_message is still not integrated in the AgentLib. # It always returns "". For now we are returning the message, # but keeping the functionality for feature use. # # @param message_buffer [FFI::MemoryPointer, nil] of type char (String) # @param stack_buffer [FFI::MemoryPointer, nil] of type char (String) # @return [String] the received error message with stack. def dl__parse_error message_buffer, stack_buffer message = dl__read_buffer(message_buffer) stack = dl__read_buffer(stack_buffer) error_message = "Message: #{ message }" error_message + " Stack Trace: #{ stack }" unless stack.empty? error_message end end end end