diff --git a/cross-project-tests/debuginfo-tests/dexter/Commands.md b/cross-project-tests/debuginfo-tests/dexter/Commands.md --- a/cross-project-tests/debuginfo-tests/dexter/Commands.md +++ b/cross-project-tests/debuginfo-tests/dexter/Commands.md @@ -9,6 +9,7 @@ * [DexLimitSteps](Commands.md#DexLimitSteps) * [DexLabel](Commands.md#DexLabel) * [DexWatch](Commands.md#DexWatch) +* [DexDeclareAddress](Commands.md#DexDeclareAddress) * [DexDeclareFile](Commands.md#DexDeclareFile) * [DexFinishTest](Commands.md#DexFinishTest) @@ -230,6 +231,61 @@ DexExpectWatchValues(..., on_line=ref('my_line_name') - 5) +### Heuristic +This command does not contribute to the heuristic score. + +---- +## DexDeclareAddress + DexDeclareAddress(declared_address, expr, **on_line[, **hit_count]) + + Args: + declared_address (str): The unique name of an address, which can be used + in DexExpectWatch-commands. + expr (str): An expression to evaluate to provide the value of this + address. + on_line (int): The line at which the value of the expression will be + assigned to the address. + hit_count (int): If provided, reads the value of the source expression + after the line has been stepped onto the given number + of times ('hit_count = 0' gives default behaviour). + +### Description +Declares a variable that can be used in DexExpectWatch- commands as an expected +value by using the `address(str[, int])` function. This is primarily +useful for checking the values of pointer variables, which are generally +determined at run-time (and so cannot be consistently matched by a hard-coded +expected value), but may be consistent relative to each other. An example use of +this command is as follows, using a set of pointer variables "foo", "bar", and +"baz": + + DexDeclareAddress('my_addr', 'bar', on_line=12) + DexExpectWatchValue('foo', address('my_addr'), on_line=10) + DexExpectWatchValue('bar', address('my_addr'), on_line=12) + DexExpectWatchValue('baz', address('my_addr', 16), on_line=14) + +On the first line, we declare the name of our variable 'my_addr'. This name must +be unique (the same name cannot be declared twice), and attempting to reference +an undeclared variable with `address` will fail. The value of the address +variable will be assigned as the value of 'bar' when line 12 is first stepped +on. + +On lines 2-4, we use the `address` function to refer to our variable. The first +usage occurs on line 10, before the line where 'my_addr' is assigned its value; +this is a valid use, as we assign the address value and check for correctness +after gathering all debug information for the test. Thus the first test command +will pass if 'foo' on line 10 has the same value as 'bar' on line 12. + +The second command will pass iff 'bar' is available at line 12 - even if the +variable and lines are identical in DexDeclareAddress and DexExpectWatchValue, +the latter will still expect a valid value. Similarly, if the variable for a +DexDeclareAddress command is not available at the given line, any test against +that address will fail. + +The `address` function also accepts an optional integer argument representing an +offset (which may be negative) to be applied to the address value, so +`address('my_addr', 16)` resolves to `my_addr + 16`. In the above example, this +means that we expect `baz == bar + 16`. + ### Heuristic This command does not contribute to the heuristic score. diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py b/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py --- a/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py @@ -19,11 +19,13 @@ from dex.command.CommandBase import CommandBase from dex.command.commands.DexDeclareFile import DexDeclareFile +from dex.command.commands.DexDeclareAddress import DexDeclareAddress from dex.command.commands.DexExpectProgramState import DexExpectProgramState from dex.command.commands.DexExpectStepKind import DexExpectStepKind from dex.command.commands.DexExpectStepOrder import DexExpectStepOrder from dex.command.commands.DexExpectWatchType import DexExpectWatchType from dex.command.commands.DexExpectWatchValue import DexExpectWatchValue +from dex.command.commands.DexExpectWatchBase import AddressExpression, DexExpectWatchBase from dex.command.commands.DexLabel import DexLabel from dex.command.commands.DexLimitSteps import DexLimitSteps from dex.command.commands.DexFinishTest import DexFinishTest @@ -39,6 +41,7 @@ { name (str): command (class) } """ return { + DexDeclareAddress.get_name() : DexDeclareAddress, DexDeclareFile.get_name() : DexDeclareFile, DexExpectProgramState.get_name() : DexExpectProgramState, DexExpectStepKind.get_name() : DexExpectStepKind, @@ -73,7 +76,7 @@ return valid_commands -def _build_command(command_type, labels, raw_text: str, path: str, lineno: str) -> CommandBase: +def _build_command(command_type, labels, addresses, raw_text: str, path: str, lineno: str) -> CommandBase: """Build a command object from raw text. This function will call eval(). @@ -90,9 +93,15 @@ return line raise format_unresolved_label_err(label_name, raw_text, path, lineno) + def get_address_object(address_name: str, offset: int=0): + if address_name not in addresses: + raise format_undeclared_address_err(address_name, raw_text, path, lineno) + return AddressExpression(address_name, offset) + valid_commands = _merge_subcommands( command_type.get_name(), { 'ref': label_to_line, + 'address': get_address_object, command_type.get_name(): command_type, }) @@ -178,6 +187,14 @@ err.info = f'Unresolved label: \'{label}\'' return err +def format_undeclared_address_err(address: str, src: str, filename: str, lineno) -> CommandParseError: + err = CommandParseError() + err.src = src + err.caret = '' # Don't bother trying to point to the bad address. + err.filename = filename + err.lineno = lineno + err.info = f'Undeclared address: \'{address}\'' + return err def format_parse_err(msg: str, path: str, lines: list, point: TextPoint) -> CommandParseError: err = CommandParseError() @@ -210,9 +227,25 @@ raise err labels[label.eval()] = label.get_line() +def add_address(addresses, address, cmd_path, cmd_lineno): + # Enforce unique address variables. + address_name = address.get_address_name() + if address_name in addresses: + err = CommandParseError() + err.info = f'Found duplicate address: \'{address_name}\'' + err.lineno = cmd_lineno + err.filename = cmd_path + err.src = address.raw_text + # Don't both trying to point to it since we're only printing the raw + # command, which isn't much text. + err.caret = '' + raise err + addresses.append(address_name) def _find_all_commands_in_file(path, file_lines, valid_commands, source_root_dir): labels = {} # dict of {name: line}. + addresses = [] # list of addresses. + address_resolutions = {} cmd_path = path declared_files = set() commands = defaultdict(dict) @@ -258,6 +291,7 @@ command = _build_command( valid_commands[command_name], labels, + addresses, raw_text, cmd_path, cmd_point.get_lineno(), @@ -277,6 +311,8 @@ else: if type(command) is DexLabel: add_line_label(labels, command, path, cmd_point.get_lineno()) + elif type(command) is DexDeclareAddress: + add_address(addresses, command, path, cmd_point.get_lineno()) elif type(command) is DexDeclareFile: cmd_path = command.declared_file if not os.path.isabs(cmd_path): diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexDeclareAddress.py b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexDeclareAddress.py new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexDeclareAddress.py @@ -0,0 +1,58 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# 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 +"""Commmand sets the path for all following commands to 'declared_file'. +""" + +import os + +from dex.command.CommandBase import CommandBase, StepExpectInfo + +class DexDeclareAddress(CommandBase): + def __init__(self, addr_name, expression, **kwargs): + + if not isinstance(addr_name, str): + raise TypeError('invalid argument type') + + self.addr_name = addr_name + self.expression = expression + self.on_line = kwargs.pop('on_line') + self.hit_count = kwargs.pop('hit_count', 0) + + self.address_resolutions = None + + super(DexDeclareAddress, self).__init__() + + @staticmethod + def get_name(): + return __class__.__name__ + + def get_watches(self): + return [StepExpectInfo(self.expression, self.path, 0, range(self.on_line, self.on_line + 1))] + + def get_address_name(self): + return self.addr_name + + def eval(self, step_collection): + assert os.path.exists(self.path) + self.address_resolutions[self.get_address_name()] = None + for step in step_collection.steps: + loc = step.current_location + + if (loc.path and os.path.exists(loc.path) and + os.path.samefile(loc.path, self.path) and + loc.lineno == self.on_line): + if self.hit_count > 0: + self.hit_count -= 1 + continue + try: + watch = step.program_state.frames[0].watches[self.expression] + except KeyError: + pass + else: + hex_val = int(watch.value, 16) + self.address_resolutions[self.get_address_name()] = hex_val + break diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexExpectWatchBase.py b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexExpectWatchBase.py --- a/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexExpectWatchBase.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexExpectWatchBase.py @@ -12,12 +12,33 @@ import abc import difflib import os +import math from collections import namedtuple from dex.command.CommandBase import CommandBase, StepExpectInfo from dex.command.StepValueInfo import StepValueInfo +class AddressExpression(object): + def __init__(self, name, offset=0): + self.name = name + self.offset = offset + def is_resolved(self, resolutions): + return self.name in resolutions + + # Given the resolved value of the address, resolve the final value of + # this expression. + def resolved_value(self, resolutions): + if not self.name in resolutions or resolutions[self.name] is None: + return None + # Technically we should fill(8) if we're debugging on a 32bit architecture? + return format_address(resolutions[self.name] + self.offset) + +def format_address(value, address_width=64): + return "0x" + hex(value)[2:].zfill(math.ceil(address_width/4)) + +def resolved_value(value, resolutions): + return value.resolved_value(resolutions) if isinstance(value, AddressExpression) else value class DexExpectWatchBase(CommandBase): def __init__(self, *args, **kwargs): @@ -25,7 +46,7 @@ raise TypeError('expected at least two args') self.expression = args[0] - self.values = [str(arg) for arg in args[1:]] + self.values = [arg if isinstance(arg, AddressExpression) else str(arg) for arg in args[1:]] try: on_line = kwargs.pop('on_line') self._from_line = on_line @@ -66,8 +87,32 @@ # unexpected value. self.unexpected_watches = [] + # List of StepValueInfos for all observed watches that were not + # invalid, irretrievable, or optimized out (combines expected and + # unexpected). + self.observed_watches = [] + + # dict of address names to their final resolved values, None until it + # gets assigned externally. + self.address_resolutions = None + super(DexExpectWatchBase, self).__init__() + def resolve_value(self, value): + return value.resolved_value(self.address_resolutions) if isinstance(value, AddressExpression) else value + + def describe_value(self, value): + if isinstance(value, AddressExpression): + offset = "" + if value.offset > 0: + offset = f"+{value.offset}" + elif value.offset < 0: + offset = str(value.offset) + desc = f"address '{value.name}'{offset}" + if self.resolve_value(value) is not None: + desc += f" ({self.resolve_value(value)})" + return desc + return value def get_watches(self): return [StepExpectInfo(self.expression, self.path, 0, range(self._from_line, self._to_line + 1))] @@ -78,11 +123,11 @@ @property def missing_values(self): - return sorted(list(self._missing_values)) + return sorted(list(self.describe_value(v) for v in self._missing_values)) @property def encountered_values(self): - return sorted(list(set(self.values) - self._missing_values)) + return sorted(list(set(self.describe_value(v) for v in set(self.values) - self._missing_values))) @abc.abstractmethod def _get_expected_field(self, watch): @@ -104,13 +149,25 @@ self.irretrievable_watches.append(step_info) return - if step_info.expected_value not in self.values: + # Check to see if this value matches with a resolved address. + matching_address = None + for v in self.values: + if (isinstance(v, AddressExpression) and + v.name in self.address_resolutions and + self.resolve_value(v) == step_info.expected_value): + matching_address = v + break + + # If this is not an expected value, either a direct value or an address, + # then this is an unexpected watch. + if step_info.expected_value not in self.values and matching_address is None: self.unexpected_watches.append(step_info) return self.expected_watches.append(step_info) + value_to_remove = matching_address if matching_address is not None else step_info.expected_value try: - self._missing_values.remove(step_info.expected_value) + self._missing_values.remove(value_to_remove) except KeyError: pass @@ -177,8 +234,9 @@ value_change_watches.append(watch) prev_value = watch.expected_value + resolved_values = [self.resolve_value(v) for v in self.values] self.misordered_watches = self._check_watch_order( value_change_watches, [ - v for v in self.values if v in + v for v in resolved_values if v in [w.expected_value for w in self.expected_watches] ]) diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/heuristic/Heuristic.py b/cross-project-tests/debuginfo-tests/dexter/dex/heuristic/Heuristic.py --- a/cross-project-tests/debuginfo-tests/dexter/dex/heuristic/Heuristic.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/heuristic/Heuristic.py @@ -15,6 +15,7 @@ import os from itertools import groupby from dex.command.StepValueInfo import StepValueInfo +from dex.command.commands.DexExpectWatchBase import format_address PenaltyCommand = namedtuple('PenaltyCommand', ['pen_dict', 'max_penalty']) @@ -101,6 +102,7 @@ def __init__(self, context, steps): self.context = context self.penalties = {} + self.address_resolutions = {} worst_penalty = max([ self.penalty_variable_optimized, self.penalty_irretrievable, @@ -109,6 +111,14 @@ self.penalty_missing_step, self.penalty_misordered_steps ]) + # Before evaluating scoring commands, evaluate address values. + try: + for command in steps.commands['DexDeclareAddress']: + command.address_resolutions = self.address_resolutions + command.eval(steps) + except KeyError: + pass + # Get DexExpectWatchType results. try: for command in steps.commands['DexExpectWatchType']: @@ -126,6 +136,7 @@ # Get DexExpectWatchValue results. try: for command in steps.commands['DexExpectWatchValue']: + command.address_resolutions = self.address_resolutions command.eval(steps) maximum_possible_penalty = min(3, len( command.values)) * worst_penalty @@ -425,6 +436,17 @@ @property def verbose_output(self): # noqa string = '' + + # Add address resolutions if present. + if self.address_resolutions: + if self.resolved_addresses: + string += '\nResolved Addresses:\n' + for addr, res in self.resolved_addresses.items(): + string += f" '{addr}': {res}\n" + if self.unresolved_addresses: + string += '\n' + string += f'Unresolved Addresses:\n {self.unresolved_addresses}\n' + string += ('\n') for command in sorted(self.penalties): pen_cmd = self.penalties[command] @@ -456,6 +478,14 @@ string += ('\n') return string + @property + def resolved_addresses(self): + return {addr: format_address(res) for addr, res in self.address_resolutions.items() if res is not None} + + @property + def unresolved_addresses(self): + return [addr for addr, res in self.address_resolutions.items() if res is None] + @property def penalty_variable_optimized(self): return self.context.options.penalty_variable_optimized diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/penalty/missing_dex_address.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/penalty/missing_dex_address.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/penalty/missing_dex_address.cpp @@ -0,0 +1,20 @@ +// Purpose: +// Test that when a \DexDeclareAddress never resolves to a value, it is +// counted as a missing value in any \DexExpectWatchValues. +// +// REQUIRES: system-linux +// +// RUN: not %dexter_regression_test -- %s | FileCheck %s +// CHECK: missing_dex_address.cpp + +int main() { + int *x = nullptr; + x = new int(5); // DexLabel('start_line') + if (false) { + (void)0; // DexLabel('unreachable') + } + delete x; // DexLabel('end_line') +} + +// DexDeclareAddress('x', 'x', on_line=ref('unreachable')) +// DexExpectWatchValue('x', 0, address('x'), from_line=ref('start_line'), to_line=ref('end_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp @@ -0,0 +1,17 @@ +// Purpose: +// Test that a \DexDeclareAddress value can have its value defined after +// the first reference to that value. +// +// REQUIRES: system-linux +// +// RUN: %dexter_regression_test -- %s | FileCheck %s +// CHECK: address_after_ref.cpp + +int main() { + int *x = new int(5); + int *y = x; // DexLabel('first_line') + delete x; // DexLabel('last_line') +} + +// DexDeclareAddress('y', 'y', on_line=ref('last_line')) +// DexExpectWatchValue('x', address('y'), on_line=ref('first_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_hit_count.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_hit_count.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_hit_count.cpp @@ -0,0 +1,20 @@ +// Purpose: +// Test that a \DexDeclareAddress command can be passed 'hit_count' as an +// optional keyword argument that captures the value of the given +// expression after the target line has been stepped on a given number of +// times. +// +// REQUIRES: system-linux +// +// RUN: %dexter_regression_test -- %s | FileCheck %s +// CHECK: address_hit_count.cpp + +int main() { + int *x = new int[3]; + for (int *y = x; y < x + 3; ++y) + *y = 0; // DexLabel('test_line') + delete x; +} + +// DexDeclareAddress('y', 'y', on_line=ref('test_line'), hit_count=2) +// DexExpectWatchValue('y', address('y', -8), address('y', -4), address('y'), on_line=ref('test_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp @@ -0,0 +1,18 @@ +// Purpose: +// Test that a \DexDeclareAddress value can be used to compare the +// addresses of two local variables that refer to the same address. +// +// REQUIRES: system-linux +// +// RUN: %dexter_regression_test -- %s | FileCheck %s +// CHECK: expression_address.cpp + +int main() { + int x = 5; + int &y = x; + x = 3; // DexLabel('test_line') +} + +// DexDeclareAddress('x_addr', '&x', on_line=ref('test_line')) +// DexExpectWatchValue('&x', address('x_addr'), on_line=ref('test_line')) +// DexExpectWatchValue('&y', address('x_addr'), on_line=ref('test_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/identical_address.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/identical_address.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/identical_address.cpp @@ -0,0 +1,18 @@ +// Purpose: +// Test that a \DexDeclareAddress value can be used to compare two equal +// pointer variables. +// +// REQUIRES: system-linux +// +// RUN: %dexter_regression_test -- %s | FileCheck %s +// CHECK: identical_address.cpp + +int main() { + int *x = new int(5); + int *y = x; + delete x; // DexLabel('test_line') +} + +// DexDeclareAddress('x', 'x', on_line=ref('test_line')) +// DexExpectWatchValue('x', address('x'), on_line=ref('test_line')) +// DexExpectWatchValue('y', address('x'), on_line=ref('test_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/multiple_address.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/multiple_address.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/multiple_address.cpp @@ -0,0 +1,24 @@ +// Purpose: +// Test that multiple \DexDeclareAddress references that point to different +// addresses can be used within a single \DexExpectWatchValue. +// +// REQUIRES: system-linux +// +// RUN: %dexter_regression_test -- %s | FileCheck %s +// CHECK: multiple_address.cpp + +int main() { + int *x = new int(5); + int *y = new int(4); + int *z = x; + *z = 0; // DexLabel('start_line') + z = y; + *z = 0; + delete x; // DexLabel('end_line') + delete y; +} + +// DexDeclareAddress('x', 'x', on_line=ref('start_line')) +// DexDeclareAddress('y', 'y', on_line=ref('start_line')) +// DexExpectWatchValue('z', address('x'), address('y'), from_line=ref('start_line'), to_line=ref('end_line')) +// DexExpectWatchValue('*z', 5, 0, 4, 0, from_line=ref('start_line'), to_line=ref('end_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/offset_address.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/offset_address.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/offset_address.cpp @@ -0,0 +1,18 @@ +// Purpose: +// Test that a \DexDeclareAddress value can be used to compare two pointer +// variables that have a fixed offset between them. +// +// REQUIRES: system-linux +// +// RUN: %dexter_regression_test -- %s | FileCheck %s +// CHECK: offset_address.cpp + +int main() { + int *x = new int[5]; + int *y = x + 3; + delete x; // DexLabel('test_line') +} + +// DexDeclareAddress('x', 'x', on_line=ref('test_line')) +// DexExpectWatchValue('x', address('x'), on_line=ref('test_line')) +// DexExpectWatchValue('y', address('x', 12), on_line=ref('test_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/self_comparison.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/self_comparison.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/self_comparison.cpp @@ -0,0 +1,18 @@ +// Purpose: +// Test that a \DexDeclareAddress value can be used to check the change in +// value of a variable over time, relative to its initial value. +// +// REQUIRES: system-linux +// +// RUN: %dexter_regression_test -- %s | FileCheck %s +// CHECK: self_comparison.cpp + +int main() { + int *x = new int[3]; + for (int *y = x; y < x + 3; ++y) + *y = 0; // DexLabel('test_line') + delete x; +} + +// DexDeclareAddress('y', 'y', on_line=ref('test_line')) +// DexExpectWatchValue('y', address('y'), address('y', 4), address('y', 8), on_line=ref('test_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/address_printing.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/address_printing.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/address_printing.cpp @@ -0,0 +1,51 @@ +// Purpose: +// Test that address values in a \DexExpectWatchValue are printed with +// their address name along with the address' resolved value (if any), and +// that when verbose output is enabled the complete map of resolved +// addresses and list of unresolved addresses will also be printed. +// +// Note: Currently "misordered result" is the only penalty that does not +// display the address properly; if it is implemented, this test should be +// updated. +// +// REQUIRES: system-linux +// +// RUN: not %dexter_regression_test -v -- %s | FileCheck %s + +// CHECK: Resolved Addresses: +// CHECK-NEXT: 'x_2': 0x[[X2_VAL:[0-9a-f]+]] +// CHECK-NEXT: 'y': 0x[[Y_VAL:[0-9a-f]+]] +// CHECK: Unresolved Addresses: +// CHECK-NEXT: ['x_1'] + +// CHECK-LABEL: [x] ExpectValue +// CHECK: expected encountered watches: +// CHECK-NEXT: address 'x_2' (0x[[X2_VAL]]) +// CHECK: missing values: +// CHECK-NEXT: address 'x_1' + +// CHECK-LABEL: [z] ExpectValue +// CHECK: expected encountered watches: +// CHECK-NEXT: address 'x_2' (0x[[X2_VAL]]) +// CHECK-NEXT: address 'y' (0x[[Y_VAL]]) +// CHECK: misordered result: +// CHECK-NEXT: step 4 (0x[[Y_VAL]]) +// CHECK-NEXT: step 5 (0x[[X2_VAL]]) + +int main() { + int *x = new int(5); + int *y = new int(4); + if (false) { + (void)0; // DexLabel('unreachable') + } + int *z = y; + z = x; // DexLabel('start_line') + delete y; + delete x; // DexLabel('end_line') +} + +// DexDeclareAddress('x_1', 'x', on_line=ref('unreachable')) +// DexDeclareAddress('x_2', 'x', on_line=ref('end_line')) +// DexDeclareAddress('y', 'y', on_line=ref('start_line')) +// DexExpectWatchValue('x', address('x_1'), address('x_2'), from_line=ref('start_line'), to_line=ref('end_line')) +// DexExpectWatchValue('z', address('x_2'), address('y'), from_line=ref('start_line'), to_line=ref('end_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_duplicate_address.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_duplicate_address.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_duplicate_address.cpp @@ -0,0 +1,16 @@ +// Purpose: +// Check that declaring duplicate addresses gives a useful error message. +// +// RUN: not %dexter_regression_test -v -- %s | FileCheck %s --match-full-lines + + +int main() { + int *result = new int(0); + delete result; // DexLabel('test_line') +} + +// CHECK: parser error:{{.*}}err_duplicate_address.cpp([[# @LINE + 4]]): Found duplicate address: 'oops' +// CHECK-NEXT: {{Dex}}DeclareAddress('oops', 'result', on_line=ref('test_line')) + +// DexDeclareAddress('oops', 'result', on_line=ref('test_line')) +// DexDeclareAddress('oops', 'result', on_line=ref('test_line')) diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_undeclared_addr.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_undeclared_addr.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_undeclared_addr.cpp @@ -0,0 +1,16 @@ +// Purpose: +// Check that using an undeclared address gives a useful error message. +// +// RUN: not %dexter_regression_test -v -- %s | FileCheck %s --match-full-lines + + +int main() { + int *result = new int(0); + delete result; // DexLabel('test_line') +} + + +// CHECK: parser error:{{.*}}err_undeclared_addr.cpp([[# @LINE + 3]]): Undeclared address: 'result' +// CHECK-NEXT: {{Dex}}ExpectWatchValue('result', address('result'), on_line=ref('test_line')) + +// DexExpectWatchValue('result', address('result'), on_line=ref('test_line'))