diff --git a/pkg/native_stack_traces/lib/src/macho.dart b/pkg/native_stack_traces/lib/src/macho.dart index 15b65f49590..adeddfa1487 100644 --- a/pkg/native_stack_traces/lib/src/macho.dart +++ b/pkg/native_stack_traces/lib/src/macho.dart @@ -125,6 +125,7 @@ class LoadCommand { static const LC_SYMTAB = 0x2; static const LC_SEGMENT_64 = 0x19; static const LC_UUID = 0x1b; + static const LC_BUILD_VERSION = 0x32; static LoadCommand fromReader(Reader reader) { final start = reader.offset; // cmdsize includes size of cmd and cmdsize. @@ -143,6 +144,9 @@ class LoadCommand { case LC_UUID: command = UuidCommand.fromReader(reader, cmd, cmdsize); break; + case LC_BUILD_VERSION: + command = BuildVersionCommand.fromReader(reader, cmd, cmdsize); + break; default: break; } @@ -351,6 +355,161 @@ class UuidCommand extends LoadCommand { } } +class Version { + final int x; + final int y; + final int z; + + const Version(this.x, this.y, this.z); + + static Version fromInt(int v) { + final x = (v & 0xffffffff) >> 16; + final y = (v & 0xffff) >> 8; + final z = v & 0xff; + return Version(x, y, z); + } + + @override + int get hashCode => Object.hash(x, y, z); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is Version && x == other.x && y == other.y && z == other.z; + } + + @override + String toString() => '$x.$y.$z'; +} + +enum Platform { + PLATFORM_UNKNOWN(0, 'Unknown'), + PLATFORM_ANY(0xFFFFFFFF, 'any'), + PLATFORM_MACOS(0x1, 'MacOS'), + PLATFORM_IOS(0x2, 'iOS'), + PLATFORM_TVOS(0x3, 'TVOS'), + PLATFORM_WATCHOS(0x4, 'WatchOS'), + PLATFORM_IOSSIMULATOR(0x7, 'iOS Simulator'), + PLATFORM_TVOSSIMULATOR(0x8, 'TVOS Simulator'), + PLATFORM_WATCHOSSIMULATOR(0x9, 'WatchOS Simulator'); + + final int id; + final String description; + + const Platform(this.id, this.description); + + static Platform? fromId(int id) { + for (final platform in values) { + if (platform.id == id) { + return platform; + } + } + return null; + } + + @override + String toString() => description; +} + +enum BuildTool { + TOOL_CLANG(1, 'clang'), + TOOL_SWIFT(2, 'Swift'), + TOOL_LD(3, 'ld'), + TOOL_LLD(4, 'lld'); + + final int id; + final String description; + + const BuildTool(this.id, this.description); + + static BuildTool? fromId(int id) { + for (final tool in values) { + if (tool.id == id) { + return tool; + } + } + return null; + } + + @override + String toString() => description; +} + +class BuildToolVersion { + final int _toolId; + final BuildTool? tool; + final Version version; + + const BuildToolVersion._(this._toolId, this.tool, this.version); + + factory BuildToolVersion.fromReader(Reader reader) { + final id = _readMachOUint32(reader); + final tool = BuildTool.fromId(id); + final version = Version.fromInt(_readMachOUint32(reader)); + return BuildToolVersion._(id, tool, version); + } + + void writeToStringBuffer(StringBuffer buffer) { + buffer + ..write(' - ') + ..write(tool ?? 'Unknown (0x${paddedHex(_toolId, 4)})') + ..write(' (') + ..write(version) + ..writeln(')'); + } + + @override + String toString() { + final buffer = StringBuffer(); + writeToStringBuffer(buffer); + return buffer.toString(); + } +} + +class BuildVersionCommand extends LoadCommand { + final int _platformId; + final Platform? platform; + final Version minOS; + final Version sdk; + final List toolVersions; + + BuildVersionCommand._(super.cmd, super.cmdsize, this._platformId, + this.platform, this.minOS, this.sdk, this.toolVersions) + : super._(); + + static BuildVersionCommand fromReader(Reader reader, int cmd, int cmdsize) { + final platformId = _readMachOUint32(reader); + final platform = Platform.fromId(platformId); + final minOS = Version.fromInt(_readMachOUint32(reader)); + final sdk = Version.fromInt(_readMachOUint32(reader)); + final toolCount = _readMachOUint32(reader); + final toolVersions = []; + for (int i = 0; i < toolCount; i++) { + toolVersions.add(BuildToolVersion.fromReader(reader)); + } + return BuildVersionCommand._( + cmd, cmdsize, platformId, platform, minOS, sdk, toolVersions); + } + + @override + void writeToStringBuffer(StringBuffer buffer) { + buffer + ..writeln('Build version:') + ..write(' Platform: ') + ..writeln(platform ?? 'Unknown (0x${paddedHex(_platformId, 4)})') + ..write(' Minimum OS: ') + ..writeln(minOS) + ..write(' Minimum SDK: ') + ..writeln(sdk); + if (toolVersions.isNotEmpty) { + buffer.writeln(' Tools:'); + for (final v in toolVersions) { + v.writeToStringBuffer(buffer); + } + } + } +} + class MachOHeader { final int magic; final int cputype; @@ -529,6 +688,9 @@ class MachO extends DwarfContainer { Reader.fromTypedData(reader.bdata, wordSize: _header.wordSize, endian: _header.endian); + Iterable commandsWhereType() => + _commands.whereType(); + @override String? get architecture => CpuType.fromCode(_header.cputype)?.dartName; diff --git a/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart b/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart index 51d46746810..efaab5b8de1 100644 --- a/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart +++ b/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart @@ -13,6 +13,7 @@ import "dart:io"; import 'package:expect/expect.dart'; import 'package:native_stack_traces/src/dwarf_container.dart'; +import 'package:native_stack_traces/src/macho.dart' as macho; import 'package:path/path.dart' as path; import 'use_flag_test_helper.dart'; @@ -85,11 +86,18 @@ Future?> retrieveDebugMap( return await runOutput(dsymutil, ['--dump-debug-map', snapshotPath]); } +final hasMinOSVersionOption = Platform.isMacOS || Platform.isIOS; +final expectedVersion = hasMinOSVersionOption ? macho.Version(1, 2, 3) : null; + Future checkSnapshotType( String tempDir, String scriptDill, SnapshotType snapshotType, ) async { + final commonOptions = []; + if (hasMinOSVersionOption && snapshotType == SnapshotType.machoDylib) { + commonOptions.add('--macho-min-os-version=$expectedVersion'); + } // Run the AOT compiler without Dwarf stack trace, once without obfuscation, // once with obfuscation, and once with obfuscation and saving debugging // information. @@ -97,8 +105,14 @@ Future checkSnapshotType( tempDir, 'unobfuscated-$snapshotType.so', ); - await createSnapshot(scriptDill, snapshotType, scriptUnobfuscatedSnapshot); + await createSnapshot( + scriptDill, + snapshotType, + scriptUnobfuscatedSnapshot, + commonOptions, + ); final unobfuscatedCase = TestCase( + snapshotType, scriptUnobfuscatedSnapshot, snapshotType.fromFile(scriptUnobfuscatedSnapshot)!, debugMap: await retrieveDebugMap(snapshotType, scriptUnobfuscatedSnapshot), @@ -109,9 +123,11 @@ Future checkSnapshotType( 'obfuscated-only-$snapshotType.so', ); await createSnapshot(scriptDill, snapshotType, scriptObfuscatedOnlySnapshot, [ + ...commonOptions, '--obfuscate', ]); final obfuscatedOnlyCase = TestCase( + snapshotType, scriptObfuscatedOnlySnapshot, snapshotType.fromFile(scriptObfuscatedOnlySnapshot)!, debugMap: await retrieveDebugMap( @@ -135,10 +151,12 @@ Future checkSnapshotType( 'obfuscated-debug-$snapshotType.so', ); await createSnapshot(scriptDill, snapshotType, scriptObfuscatedSnapshot, [ + ...commonOptions, '--obfuscate', '--save-debugging-info=$scriptDebuggingInfo', ]); obfuscatedCase = TestCase( + snapshotType, scriptObfuscatedSnapshot, snapshotType.fromFile(scriptObfuscatedSnapshot)!, debuggingInfoContainer: snapshotType.fromFile(scriptDebuggingInfo)!, @@ -154,11 +172,13 @@ Future checkSnapshotType( 'obfuscated-separate-debug-$snapshotType.so', ); await createSnapshot(scriptDill, snapshotType, scriptStrippedSnapshot, [ + ...commonOptions, '--strip', '--obfuscate', '--save-debugging-info=$scriptSeparateDebuggingInfo', ]); strippedCase = TestCase( + snapshotType, scriptStrippedSnapshot, /*container=*/ null, // No static symbols in stripped snapshot. debuggingInfoContainer: snapshotType.fromFile( @@ -192,12 +212,14 @@ Future checkAssembly(String tempDir, String scriptDill) async { } class TestCase { + final SnapshotType type; final String snapshotPath; final DwarfContainer? container; final DwarfContainer? debuggingInfoContainer; final List? debugMap; TestCase( + this.type, this.snapshotPath, this.container, { this.debuggingInfoContainer, @@ -222,6 +244,7 @@ Future checkCases( unstrippedObfuscateds.map((c) => c.debugMap!).toList(), ); } + checkMachOSnapshots(unobfuscated, obfuscateds); } Future checkTraces( @@ -412,3 +435,31 @@ void checkDebugMaps(List expected, List> cases) { } } } + +// Checks for MachO snapshots (not separate debugging information). +void checkMachOSnapshots(TestCase unobfuscated, List obfuscateds) { + checkMachOSnapshot(unobfuscated); + obfuscateds.forEach(checkMachOSnapshot); +} + +void checkMachOSnapshot(TestCase testCase) { + // The checks below are only for snapshots, not for debugging information. + final snapshot = testCase.container; + if (snapshot is! macho.MachO) return; + final buildVersion = snapshot + .commandsWhereType() + .singleOrNull; + final expectedPlatform = Platform.isMacOS + ? macho.Platform.PLATFORM_MACOS + : Platform.isIOS + ? macho.Platform.PLATFORM_IOS + : null; + Expect.equals(expectedPlatform, buildVersion?.platform); + if (testCase.type == SnapshotType.machoDylib) { + Expect.equals(expectedVersion, buildVersion?.minOS); + Expect.equals(expectedVersion, buildVersion?.sdk); + if (buildVersion != null) { + Expect.isEmpty(buildVersion.toolVersions); + } + } +} diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart index b49e3142f42..298665b9fbb 100644 --- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart +++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart @@ -16,7 +16,8 @@ import "dart:io"; import 'package:native_stack_traces/native_stack_traces.dart'; import 'package:native_stack_traces/src/constants.dart' show rootLoadingUnitId; -import 'package:native_stack_traces/src/macho.dart'; +import 'package:native_stack_traces/src/macho.dart' + show emptyMachOForArchitecture, MachO; import 'package:path/path.dart' as path; import 'package:test/test.dart'; diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart index 4a502905851..9ec337015e3 100644 --- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart +++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart @@ -10,7 +10,8 @@ import "dart:async"; import "dart:io"; import 'package:native_stack_traces/native_stack_traces.dart'; -import 'package:native_stack_traces/src/macho.dart'; +import 'package:native_stack_traces/src/macho.dart' + show emptyMachOForArchitecture, MachO; import 'package:path/path.dart' as path; import 'package:test/test.dart'; diff --git a/runtime/vm/mach_o.cc b/runtime/vm/mach_o.cc index 58c88d24532..8fbb5c2729c 100644 --- a/runtime/vm/mach_o.cc +++ b/runtime/vm/mach_o.cc @@ -14,6 +14,7 @@ #include "vm/compiler/runtime_api.h" #include "vm/dwarf.h" #include "vm/dwarf_so_writer.h" +#include "vm/flags.h" #include "vm/hash_map.h" #include "vm/image_snapshot.h" #include "vm/os.h" @@ -22,6 +23,13 @@ namespace dart { +#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) +DEFINE_FLAG(charp, + macho_min_os_version, + nullptr, + "The minimum OS version required for MacOS/iOS Mach-O snapshots"); +#endif + static constexpr intptr_t kLinearInitValue = -1; #define DEFINE_LINEAR_FIELD_METHODS(name) \ @@ -53,17 +61,24 @@ static constexpr intptr_t kLinearInitValue = -1; return true; \ } +#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) +#define FOR_EACH_MACOS_ONLY_CONCRETE_MACHO_CONTENTS_TYPE(V) \ + V(MachOBuildVersion) \ + V(MachOLoadDylib) +#else +#define FOR_EACH_MACOS_ONLY_CONCRETE_MACHO_CONTENTS_TYPE(V) +#endif + // All concrete subclasses of MachOContents should go here: #define FOR_EACH_CONCRETE_MACHO_CONTENTS_TYPE(V) \ + FOR_EACH_MACOS_ONLY_CONCRETE_MACHO_CONTENTS_TYPE(V) \ V(MachOHeader) \ V(MachOSegment) \ V(MachOSection) \ V(MachOSymbolTable) \ V(MachODynamicSymbolTable) \ V(MachOUuid) \ - V(MachOBuildVersion) \ V(MachOIdDylib) \ - V(MachOLoadDylib) \ V(MachOCodeSignature) #define DECLARE_CONTENTS_TYPE_CLASS(Type) class Type; @@ -924,70 +939,6 @@ class MachOUuid : public MachOCommand { #define MACHO_XYZ_VERSION_ENCODING(x, y, z) \ static_cast(((x) << 16) | ((y) << 8) | (z)) -class MachOBuildVersion : public MachOCommand { - public: - static constexpr uint32_t kCommandCode = mach_o::LC_BUILD_VERSION; - - MachOBuildVersion() - : MachOCommand(kCommandCode, - /*needs_offset=*/false, - /*in_segment=*/false) {} - - uint32_t cmdsize() const override { - return sizeof(mach_o::build_version_command); - } - - uint32_t platform() const { -#if defined(DART_TARGET_OS_MACOS_IOS) - return mach_o::PLATFORM_IOS; -#elif defined(DART_TARGET_OS_MACOS) - return mach_o::PLATFORM_MACOS; -#else - return mach_o::PLATFORM_UNKNOWN; -#endif - } - - uint32_t minos() const { -#if defined(DART_TARGET_OS_MACOS_IOS) - // TODO(sstrickl): No minimum version for iOS currently defined. - UNIMPLEMENTED(); -#elif defined(DART_TARGET_OS_MACOS) - return kMinMacOSVersion; -#else - return 0; // No version for the unknown platform. -#endif - } - - uint32_t sdk() const { -#if defined(DART_TARGET_OS_MACOS_IOS) - // TODO(sstrickl): No SDK version for iOS currently defined. - UNIMPLEMENTED(); -#elif defined(DART_TARGET_OS_MACOS) - return kMacOSSdkVersion; -#else - return 0; // No version for the unknown platform. -#endif - } - - void WriteLoadCommand(MachOWriteStream* stream) const override { - MachOCommand::WriteLoadCommand(stream); - stream->Write32(platform()); - stream->Write32(minos()); - stream->Write32(sdk()); - stream->Write32(0); // No tool versions. - } - - void Accept(Visitor* visitor) override { - visitor->VisitMachOBuildVersion(this); - } - - private: - static constexpr auto kMinMacOSVersion = MACHO_XYZ_VERSION_ENCODING(15, 0, 0); - static constexpr auto kMacOSSdkVersion = MACHO_XYZ_VERSION_ENCODING(15, 4, 0); - - DISALLOW_COPY_AND_ASSIGN(MachOBuildVersion); -}; - class MachODylib : public MachOCommand { public: uint32_t cmdsize() const override { @@ -1058,6 +1009,7 @@ class MachOIdDylib : public MachODylib { DISALLOW_COPY_AND_ASSIGN(MachOIdDylib); }; +#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) class MachOLoadDylib : public MachODylib { public: static constexpr uint32_t kCommandCode = mach_o::LC_LOAD_DYLIB; @@ -1089,6 +1041,139 @@ class MachOLoadDylib : public MachODylib { DISALLOW_COPY_AND_ASSIGN(MachOLoadDylib); }; +class Version { + public: + explicit Version(intptr_t major) : Version(major, 0, 0) {} + + Version(intptr_t major, intptr_t minor) : Version(major, minor, 0) {} + + Version(intptr_t major, intptr_t minor, intptr_t patch) + : major_(major), minor_(minor), patch_(patch) { + ASSERT(Utils::IsUint(16, major)); + ASSERT(Utils::IsUint(8, minor)); + ASSERT(Utils::IsUint(8, patch)); + } + + Version(const Version& other) + : major_(other.major_), minor_(other.minor_), patch_(other.patch_) {} + + static Version FromString(const char* str) { + ASSERT(str != nullptr); + int64_t major = 0; + int64_t minor = 0; + int64_t patch = 0; + char* current = nullptr; + if (!OS::ParseInitialInt64(str, &major, ¤t)) { + FATAL("Expected an integer, got %s", str); + } + if (!Utils::IsUint(16, major)) { + FATAL("Major version is too large to represent in 16 bits: %" Pd64, + major); + } + if (*current != '\0' && *current != '.') { + FATAL("Unexpected characters when parsing version: %s", current); + } + if (*current == '.') { + if (!OS::ParseInitialInt64(current + 1, &minor, ¤t)) { + FATAL("Expected an integer, got %s", str); + } + if (!Utils::IsUint(8, minor)) { + FATAL("Minor version is too large to represent in 8 bits: %" Pd64, + minor); + } + if (*current != '\0' && *current != '.') { + FATAL("Unexpected characters when parsing version: %s", current); + } + if (*current == '.') { + if (!OS::ParseInitialInt64(current + 1, &patch, ¤t)) { + FATAL("Expected an integer, got %s", str); + } + if (!Utils::IsUint(8, patch)) { + FATAL("Patch version is too large to represent in 8 bits: %" Pd64, + patch); + } + if (*current != '\0') { + FATAL("Unexpected characters when parsing version: %s", current); + } + } + } + return Version(major, minor, patch); + } + + void Write(MachOWriteStream* stream) const { + stream->Write32(MACHO_XYZ_VERSION_ENCODING(major_, minor_, patch_)); + } + + const char* ToCString() const { + return OS::SCreate(Thread::Current()->zone(), "%" Pd ".%" Pd ".%" Pd "", + major_, minor_, patch_); + } + + private: + const intptr_t major_; + const intptr_t minor_; + const intptr_t patch_; + DISALLOW_ALLOCATION(); + void operator=(const Version&) = delete; +}; + +// These defaults were taken from Flutter at the time of editing, but can be +// overridden using the --min-ios-version and --min-macos-version flags. +#if defined(DART_TARGET_OS_MACOS_IOS) +static const Version kDefaultMinOSVersion(13, 0, 0); // iOS 13 +#else +static const Version kDefaultMinOSVersion(10, 15, 0); // MacOS Catalina (10.15) +#endif + +class MachOBuildVersion : public MachOCommand { + public: + static constexpr uint32_t kCommandCode = mach_o::LC_BUILD_VERSION; + + MachOBuildVersion() + : MachOCommand(kCommandCode, + /*needs_offset=*/false, + /*in_segment=*/false), + min_os_(FLAG_macho_min_os_version != nullptr + ? Version::FromString(FLAG_macho_min_os_version) + : kDefaultMinOSVersion) {} + + uint32_t cmdsize() const override { + return sizeof(mach_o::build_version_command); + } + + uint32_t platform() const { +#if defined(DART_TARGET_OS_MACOS_IOS) + return mach_o::PLATFORM_IOS; +#else + return mach_o::PLATFORM_MACOS; +#endif + } + + const Version& minos() const { return min_os_; } + + const Version& sdk() const { + // Just use the minimum version as the targeted version. + return minos(); + } + + void WriteLoadCommand(MachOWriteStream* stream) const override { + MachOCommand::WriteLoadCommand(stream); + stream->Write32(platform()); + minos().Write(stream); + sdk().Write(stream); + stream->Write32(0); // No tool versions. + } + + void Accept(Visitor* visitor) override { + visitor->VisitMachOBuildVersion(this); + } + + private: + const Version min_os_; + + DISALLOW_COPY_AND_ASSIGN(MachOBuildVersion); +}; +#endif #undef MACHO_XYZ_VERSION_ENCODING class MachOSymbolTable : public MachOCommand { @@ -2310,15 +2395,14 @@ void MachOHeader::GenerateUnwindingInformation() { } void MachOHeader::GenerateMiscellaneousCommands() { - // Not idempotent; - ASSERT(!HasCommand(MachOBuildVersion::kCommandCode)); - ASSERT(!HasCommand(MachOIdDylib::kCommandCode)); - ASSERT(!HasCommand(MachOLoadDylib::kCommandCode)); - - commands_.Add(new (zone_) MachOBuildVersion()); if (type_ == SnapshotType::Snapshot) { + // Not idempotent; + ASSERT(!HasCommand(MachOIdDylib::kCommandCode)); commands_.Add(new (zone_) MachOIdDylib(identifier_)); #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) + ASSERT(!HasCommand(MachOBuildVersion::kCommandCode)); + ASSERT(!HasCommand(MachOLoadDylib::kCommandCode)); + commands_.Add(new (zone_) MachOBuildVersion()); commands_.Add(MachOLoadDylib::CreateLoadSystemDylib(zone_)); #endif } diff --git a/runtime/vm/os.h b/runtime/vm/os.h index 29875c4a9be..f5a91998c68 100644 --- a/runtime/vm/os.h +++ b/runtime/vm/os.h @@ -99,13 +99,27 @@ class OS { PRINTF_ATTRIBUTE(2, 3); static char* VSCreate(Zone* zone, const char* format, va_list args); + // Converts the initial portion of a C string which represents a valid dart + // integer into a 64 bit value. + // + // Returns false if it is unable to convert the string to a 64 bit value, + // the failure could be because of underflow/overflow or invalid characters. + // + // On success the function returns true, 'value' contains the converted + // value, and 'end' contains a pointer to the next character + // after the parsed integer. + static bool ParseInitialInt64(const char* str, int64_t* value, char** end); + // Converts a C string which represents a valid dart integer into a 64 bit // value. // Returns false if it is unable to convert the string to a 64 bit value, // the failure could be because of underflow/overflow or invalid characters. // On success the function returns true and 'value' contains the converted // value. - static bool StringToInt64(const char* str, int64_t* value); + static bool StringToInt64(const char* str, int64_t* value) { + char* endptr = nullptr; + return ParseInitialInt64(str, value, &endptr) && (*endptr == '\0'); + } // Register code observers relevant to this OS. static void RegisterCodeObservers(); diff --git a/runtime/vm/os_android.cc b/runtime/vm/os_android.cc index 64a5d53a6e3..5547201ae10 100644 --- a/runtime/vm/os_android.cc +++ b/runtime/vm/os_android.cc @@ -303,10 +303,10 @@ char* OS::VSCreate(Zone* zone, const char* format, va_list args) { return buffer; } -bool OS::StringToInt64(const char* str, int64_t* value) { - ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr); +bool OS::ParseInitialInt64(const char* str, int64_t* value, char** end) { + ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr && + end != nullptr); int32_t base = 10; - char* endptr; int i = 0; if (str[0] == '-') { i = 1; @@ -321,11 +321,11 @@ bool OS::StringToInt64(const char* str, int64_t* value) { if (base == 16) { // Unsigned 64-bit hexadecimal integer literals are allowed but // immediately interpreted as signed 64-bit integers. - *value = static_cast(strtoull(str, &endptr, base)); + *value = static_cast(strtoull(str, end, base)); } else { - *value = strtoll(str, &endptr, base); + *value = strtoll(str, end, base); } - return ((errno == 0) && (endptr != str) && (*endptr == 0)); + return (errno == 0) && (*end != str); } void OS::RegisterCodeObservers() { diff --git a/runtime/vm/os_fuchsia.cc b/runtime/vm/os_fuchsia.cc index eb3860e8707..4f6b563d9b7 100644 --- a/runtime/vm/os_fuchsia.cc +++ b/runtime/vm/os_fuchsia.cc @@ -560,10 +560,10 @@ char* OS::VSCreate(Zone* zone, const char* format, va_list args) { return buffer; } -bool OS::StringToInt64(const char* str, int64_t* value) { - ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr); +bool OS::ParseInitialInt64(const char* str, int64_t* value, char** end) { + ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr && + end != nullptr); int32_t base = 10; - char* endptr; int i = 0; if (str[0] == '-') { i = 1; @@ -578,11 +578,11 @@ bool OS::StringToInt64(const char* str, int64_t* value) { if (base == 16) { // Unsigned 64-bit hexadecimal integer literals are allowed but // immediately interpreted as signed 64-bit integers. - *value = static_cast(strtoull(str, &endptr, base)); + *value = static_cast(strtoull(str, end, base)); } else { - *value = strtoll(str, &endptr, base); + *value = strtoll(str, end, base); } - return ((errno == 0) && (endptr != str) && (*endptr == 0)); + return (errno == 0) && (*end != str); } void OS::RegisterCodeObservers() { diff --git a/runtime/vm/os_linux.cc b/runtime/vm/os_linux.cc index f8c7bc519d9..a09fbfddc44 100644 --- a/runtime/vm/os_linux.cc +++ b/runtime/vm/os_linux.cc @@ -616,10 +616,10 @@ char* OS::VSCreate(Zone* zone, const char* format, va_list args) { return buffer; } -bool OS::StringToInt64(const char* str, int64_t* value) { - ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr); +bool OS::ParseInitialInt64(const char* str, int64_t* value, char** end) { + ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr && + end != nullptr); int32_t base = 10; - char* endptr; int i = 0; if (str[0] == '-') { i = 1; @@ -634,11 +634,11 @@ bool OS::StringToInt64(const char* str, int64_t* value) { if (base == 16) { // Unsigned 64-bit hexadecimal integer literals are allowed but // immediately interpreted as signed 64-bit integers. - *value = static_cast(strtoull(str, &endptr, base)); + *value = static_cast(strtoull(str, end, base)); } else { - *value = strtoll(str, &endptr, base); + *value = strtoll(str, end, base); } - return ((errno == 0) && (endptr != str) && (*endptr == 0)); + return (errno == 0) && (*end != str); } void OS::RegisterCodeObservers() { diff --git a/runtime/vm/os_macos.cc b/runtime/vm/os_macos.cc index 4a84c7c2650..b11ef122dd2 100644 --- a/runtime/vm/os_macos.cc +++ b/runtime/vm/os_macos.cc @@ -223,10 +223,10 @@ char* OS::VSCreate(Zone* zone, const char* format, va_list args) { return buffer; } -bool OS::StringToInt64(const char* str, int64_t* value) { - ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr); +bool OS::ParseInitialInt64(const char* str, int64_t* value, char** end) { + ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr && + end != nullptr); int32_t base = 10; - char* endptr; int i = 0; if (str[0] == '-') { i = 1; @@ -241,11 +241,11 @@ bool OS::StringToInt64(const char* str, int64_t* value) { if (base == 16) { // Unsigned 64-bit hexadecimal integer literals are allowed but // immediately interpreted as signed 64-bit integers. - *value = static_cast(strtoull(str, &endptr, base)); + *value = static_cast(strtoull(str, end, base)); } else { - *value = strtoll(str, &endptr, base); + *value = strtoll(str, end, base); } - return ((errno == 0) && (endptr != str) && (*endptr == 0)); + return (errno == 0) && (*end != str); } void OS::RegisterCodeObservers() {} diff --git a/runtime/vm/os_win.cc b/runtime/vm/os_win.cc index b0472b8891f..1c510d2bec8 100644 --- a/runtime/vm/os_win.cc +++ b/runtime/vm/os_win.cc @@ -328,10 +328,10 @@ char* OS::VSCreate(Zone* zone, const char* format, va_list args) { return buffer; } -bool OS::StringToInt64(const char* str, int64_t* value) { - ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr); +bool OS::ParseInitialInt64(const char* str, int64_t* value, char** end) { + ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr && + end != nullptr); int32_t base = 10; - char* endptr; int i = 0; if (str[0] == '-') { i = 1; @@ -346,11 +346,11 @@ bool OS::StringToInt64(const char* str, int64_t* value) { if (base == 16) { // Unsigned 64-bit hexadecimal integer literals are allowed but // immediately interpreted as signed 64-bit integers. - *value = static_cast(_strtoui64(str, &endptr, base)); + *value = static_cast(_strtoui64(str, end, base)); } else { - *value = _strtoi64(str, &endptr, base); + *value = _strtoi64(str, end, base); } - return ((errno == 0) && (endptr != str) && (*endptr == 0)); + return (errno == 0) && (*end != str); } void OS::RegisterCodeObservers() {}