Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2018 Philip Chimento <philip.chimento@gmail.com>
4 : :
5 : : #include <config.h>
6 : :
7 : : #include <cstddef> // for size_t
8 : : #include <functional> // for hash<int>
9 : : #include <sstream>
10 : : #include <string> // for string
11 : : #include <string_view>
12 : : #include <unordered_set> // for unordered_set
13 : : #include <utility> // for move
14 : : #include <vector>
15 : :
16 : : #include <glib.h> // for g_warning
17 : :
18 : : #include <js/CharacterEncoding.h>
19 : : #include <js/Conversions.h>
20 : : #include <js/RootingAPI.h>
21 : : #include <js/Stack.h> // for CaptureCurrentStack, MaxFrames
22 : : #include <js/TypeDecls.h>
23 : : #include <js/Utility.h> // for UniqueChars
24 : : #include <js/Value.h>
25 : : #include <js/friend/DumpFunctions.h>
26 : :
27 : : #include "gjs/deprecation.h"
28 : : #include "gjs/macros.h"
29 : :
30 : : const char* messages[] = {
31 : : // None:
32 : : "(invalid message)",
33 : :
34 : : // ByteArrayInstanceToString:
35 : : "Some code called array.toString() on a Uint8Array instance. Previously "
36 : : "this would have interpreted the bytes of the array as a string, but that "
37 : : "is nonstandard. In the future this will return the bytes as "
38 : : "comma-separated digits. For the time being, the old behavior has been "
39 : : "preserved, but please fix your code anyway to use TextDecoder.\n"
40 : : "(Note that array.toString() may have been called implicitly.)",
41 : :
42 : : // DeprecatedGObjectProperty:
43 : : "The GObject property {}.{} is deprecated.",
44 : :
45 : : // ModuleExportedLetOrConst:
46 : : "Some code accessed the property '{}' on the module '{}'. That property "
47 : : "was defined with 'let' or 'const' inside the module. This was previously "
48 : : "supported, but is not correct according to the ES6 standard. Any symbols "
49 : : "to be exported from a module must be defined with 'var'. The property "
50 : : "access will work as previously for the time being, but please fix your "
51 : : "code anyway.",
52 : :
53 : : // PlatformSpecificTypelib:
54 : : ("{} has been moved to a separate platform-specific library. Please update "
55 : : "your code to use {} instead."),
56 : : };
57 : :
58 : : static_assert(G_N_ELEMENTS(messages) == GjsDeprecationMessageId::LastValue);
59 : :
60 : : struct DeprecationEntry {
61 : : GjsDeprecationMessageId id;
62 : : std::string loc;
63 : :
64 : 17 : DeprecationEntry(GjsDeprecationMessageId an_id, const char* a_loc)
65 [ + - ]: 34 : : id(an_id), loc(a_loc ? a_loc : "unknown") {}
66 : :
67 : 0 : bool operator==(const DeprecationEntry& other) const {
68 [ # # # # ]: 0 : return id == other.id && loc == other.loc;
69 : : }
70 : : };
71 : :
72 : : namespace std {
73 : : template <>
74 : : struct hash<DeprecationEntry> {
75 : 34 : size_t operator()(const DeprecationEntry& key) const {
76 : 34 : return hash<int>()(key.id) ^ hash<std::string>()(key.loc);
77 : : }
78 : : };
79 : : }; // namespace std
80 : :
81 : : static std::unordered_set<DeprecationEntry> logged_messages;
82 : :
83 : : GJS_JSAPI_RETURN_CONVENTION
84 : 17 : static JS::UniqueChars get_callsite(JSContext* cx,
85 : : unsigned max_frames /* = 1 */) {
86 : 17 : JS::RootedObject stack_frame(cx);
87 : 17 : if (!JS::CaptureCurrentStack(cx, &stack_frame,
88 [ + - + - : 51 : JS::StackCapture{JS::MaxFrames{max_frames}}) ||
- + ]
89 [ - + ]: 17 : !stack_frame)
90 : 0 : return nullptr;
91 : :
92 : 17 : JS::RootedValue v_frame(cx, JS::ObjectValue(*stack_frame));
93 : 17 : JS::RootedString frame_string(cx, JS::ToString(cx, v_frame));
94 [ - + ]: 17 : if (!frame_string)
95 : 0 : return nullptr;
96 : :
97 : 17 : return JS_EncodeStringToUTF8(cx, frame_string);
98 : 17 : }
99 : :
100 : 17 : static void warn_deprecated_unsafe_internal(JSContext* cx,
101 : : const GjsDeprecationMessageId id,
102 : : const char* msg,
103 : : unsigned max_frames /* = 1 */) {
104 : 17 : JS::UniqueChars callsite{get_callsite(cx, max_frames)};
105 : 17 : DeprecationEntry entry(id, callsite.get());
106 [ + - ]: 17 : if (!logged_messages.count(entry)) {
107 : : JS::UniqueChars stack_dump =
108 : 17 : JS::FormatStackDump(cx, false, false, false);
109 : 17 : g_warning("%s\n%s", msg, stack_dump.get());
110 : 17 : logged_messages.insert(std::move(entry));
111 : 17 : }
112 : 17 : }
113 : :
114 : : /* Note, this can only be called from the JS thread because it uses the full
115 : : * stack dump API and not the "safe" gjs_dumpstack() which can only print to
116 : : * stdout or stderr. Do not use this function during GC, for example. */
117 : 2 : void _gjs_warn_deprecated_once_per_callsite(JSContext* cx,
118 : : const GjsDeprecationMessageId id,
119 : : unsigned max_frames) {
120 : 2 : warn_deprecated_unsafe_internal(cx, id, messages[id], max_frames);
121 : 2 : }
122 : :
123 : 15 : void _gjs_warn_deprecated_once_per_callsite(
124 : : JSContext* cx, GjsDeprecationMessageId id,
125 : : const std::vector<std::string>& args, unsigned max_frames) {
126 : : // In C++20, use std::format() for this
127 : 15 : std::string_view format_string{messages[id]};
128 : 15 : std::stringstream message;
129 : :
130 : 15 : size_t pos = 0;
131 : 15 : size_t copied = 0;
132 : 15 : size_t args_ptr = 0;
133 : 15 : size_t nargs_given = args.size();
134 : :
135 [ + + ]: 45 : while ((pos = format_string.find("{}", pos)) != std::string::npos) {
136 [ - + ]: 30 : if (args_ptr >= nargs_given) {
137 : 0 : g_critical("Only %zu format args passed for message ID %u",
138 : : nargs_given, unsigned{id});
139 : 0 : return;
140 : : }
141 : :
142 : 30 : message << format_string.substr(copied, pos - copied);
143 : 30 : message << args[args_ptr++];
144 : 30 : pos = copied = pos + 2; // skip over braces
145 : : }
146 [ - + ]: 15 : if (args_ptr != nargs_given) {
147 : 0 : g_critical("Excess %zu format args passed for message ID %u",
148 : : nargs_given, unsigned{id});
149 : 0 : return;
150 : : }
151 : :
152 : 15 : message << format_string.substr(copied, std::string::npos);
153 : :
154 : 15 : std::string message_formatted = message.str();
155 : 15 : warn_deprecated_unsafe_internal(cx, id, message_formatted.c_str(),
156 : : max_frames);
157 [ + - ]: 15 : }
|