From 0428d44d4cfafe55fd13679cf16f6b099cbb0e61 Mon Sep 17 00:00:00 2001 From: Stephen Tozer Date: Wed, 1 Dec 2021 13:00:22 +0000 Subject: [PATCH] [Dexter] Add DexDeclareAddress command and address function This patch adds a new dexter command, DexDeclareAddress, which is used to test the relative values of pointer variables. The motivation for adding this command is to allow meaningful assertions to be made about pointers that go beyond checking variable availability and null equality. The full explanation and syntax is in Commands.md. Reviewed By: Orlando Differential Revision: https://reviews.llvm.org/D111447 --- .../debuginfo-tests/dexter/Commands.md | 56 +++++++++++++++++ .../dexter/dex/command/ParseCommand.py | 38 +++++++++++- .../dex/command/commands/DexDeclareAddress.py | 58 ++++++++++++++++++ .../dex/command/commands/DexExpectWatchBase.py | 70 ++++++++++++++++++++-- .../dexter/dex/heuristic/Heuristic.py | 30 ++++++++++ .../commands/penalty/missing_dex_address.cpp | 20 +++++++ .../dex_declare_address/address_after_ref.cpp | 17 ++++++ .../dex_declare_address/address_hit_count.cpp | 20 +++++++ .../dex_declare_address/expression_address.cpp | 18 ++++++ .../dex_declare_address/identical_address.cpp | 18 ++++++ .../dex_declare_address/multiple_address.cpp | 24 ++++++++ .../perfect/dex_declare_address/offset_address.cpp | 18 ++++++ .../dex_declare_address/self_comparison.cpp | 18 ++++++ .../subtools/test/address_printing.cpp | 51 ++++++++++++++++ .../subtools/test/err_duplicate_address.cpp | 16 +++++ .../subtools/test/err_undeclared_addr.cpp | 16 +++++ 16 files changed, 481 insertions(+), 7 deletions(-) create mode 100644 cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexDeclareAddress.py create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/penalty/missing_dex_address.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_hit_count.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/identical_address.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/multiple_address.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/offset_address.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/self_comparison.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/address_printing.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_duplicate_address.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_undeclared_addr.cpp diff --git a/cross-project-tests/debuginfo-tests/dexter/Commands.md b/cross-project-tests/debuginfo-tests/dexter/Commands.md index 4ea24b3..24f6f32 100644 --- 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) @@ -234,6 +235,61 @@ arithmetic operators to get offsets from labels: 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. + +---- ## DexDeclareFile DexDeclareFile(declared_file) diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py b/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py index 40cb36a..b0e7646 100644 --- 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.utils.Exceptions import CommandParseError 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 @@ def _get_valid_commands(): { 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 @@ def _merge_subcommands(command_name: str, valid_commands: dict) -> dict: 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 @@ def _build_command(command_type, labels, raw_text: str, path: str, lineno: str) 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 @@ def format_unresolved_label_err(label: str, src: str, filename: str, lineno) -> 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 @@ def add_line_label(labels, label, cmd_path, cmd_lineno): 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 @@ def _find_all_commands_in_file(path, file_lines, valid_commands, source_root_dir command = _build_command( valid_commands[command_name], labels, + addresses, raw_text, cmd_path, cmd_point.get_lineno(), @@ -277,6 +311,8 @@ def _find_all_commands_in_file(path, file_lines, valid_commands, source_root_dir 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 index 0000000..cf54760 --- /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 index 1c2d544..44c8bdb 100644 --- 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 @@ class DexExpectWatchBase(CommandBase): 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 @@ class DexExpectWatchBase(CommandBase): # 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 @@ class DexExpectWatchBase(CommandBase): @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 @@ class DexExpectWatchBase(CommandBase): 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 @@ class DexExpectWatchBase(CommandBase): 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 index 205b767..0305c83 100644 --- 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 difflib 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 @@ class Heuristic(object): 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 @@ class Heuristic(object): 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 @@ class Heuristic(object): # 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 @@ class Heuristic(object): @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] @@ -457,6 +479,14 @@ class Heuristic(object): 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 index 0000000..0d3cc09 --- /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 index 0000000..6ce9b8a --- /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 index 0000000..c064ea90d --- /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 index 0000000..72ad119 --- /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 index 0000000..ea495d3 --- /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 index 0000000..c8bcbea --- /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 index 0000000..e778cd1 --- /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 index 0000000..77801b7 --- /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 index 0000000..0ee8a37 --- /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 index 0000000..23bc1a5 --- /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 index 0000000..baae1ac --- /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')) -- 2.7.4