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 : 5 : DeprecationEntry(GjsDeprecationMessageId an_id, const char* a_loc)
65 : 10 : : id(an_id), loc(a_loc) {}
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 : 7 : size_t operator()(const DeprecationEntry& key) const {
76 : 7 : 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 : 5 : static JS::UniqueChars get_callsite(JSContext* cx) {
85 : 5 : JS::RootedObject stack_frame(cx);
86 : 5 : if (!JS::CaptureCurrentStack(cx, &stack_frame,
87 [ + - + - : 15 : JS::StackCapture(JS::MaxFrames(1))) ||
- + ]
88 [ - + ]: 5 : !stack_frame)
89 : 0 : return nullptr;
90 : :
91 : 5 : JS::RootedValue v_frame(cx, JS::ObjectValue(*stack_frame));
92 : 5 : JS::RootedString frame_string(cx, JS::ToString(cx, v_frame));
93 [ - + ]: 5 : if (!frame_string)
94 : 0 : return nullptr;
95 : :
96 : 5 : return JS_EncodeStringToUTF8(cx, frame_string);
97 : 5 : }
98 : :
99 : 5 : static void warn_deprecated_unsafe_internal(JSContext* cx,
100 : : const GjsDeprecationMessageId id,
101 : : const char* msg) {
102 : 5 : JS::UniqueChars callsite(get_callsite(cx));
103 : 5 : DeprecationEntry entry(id, callsite.get());
104 [ + - ]: 5 : if (!logged_messages.count(entry)) {
105 : : JS::UniqueChars stack_dump =
106 : 5 : JS::FormatStackDump(cx, false, false, false);
107 : 5 : g_warning("%s\n%s", msg, stack_dump.get());
108 : 5 : logged_messages.insert(std::move(entry));
109 : 5 : }
110 : 5 : }
111 : :
112 : : /* Note, this can only be called from the JS thread because it uses the full
113 : : * stack dump API and not the "safe" gjs_dumpstack() which can only print to
114 : : * stdout or stderr. Do not use this function during GC, for example. */
115 : 2 : void _gjs_warn_deprecated_once_per_callsite(JSContext* cx,
116 : : const GjsDeprecationMessageId id) {
117 : 2 : warn_deprecated_unsafe_internal(cx, id, messages[id]);
118 : 2 : }
119 : :
120 : 3 : void _gjs_warn_deprecated_once_per_callsite(
121 : : JSContext* cx, GjsDeprecationMessageId id,
122 : : const std::vector<const char*>& args) {
123 : : // In C++20, use std::format() for this
124 : 3 : std::string_view format_string{messages[id]};
125 : 3 : std::stringstream message;
126 : :
127 : 3 : size_t pos = 0;
128 : 3 : size_t copied = 0;
129 : 3 : size_t args_ptr = 0;
130 : 3 : size_t nargs_given = args.size();
131 : :
132 [ + + ]: 9 : while ((pos = format_string.find("{}", pos)) != std::string::npos) {
133 [ - + ]: 6 : if (args_ptr >= nargs_given) {
134 : 0 : g_critical("Only %zu format args passed for message ID %u",
135 : : nargs_given, unsigned{id});
136 : 0 : return;
137 : : }
138 : :
139 : 6 : message << format_string.substr(copied, pos - copied);
140 : 6 : message << args[args_ptr++];
141 : 6 : pos = copied = pos + 2; // skip over braces
142 : : }
143 [ - + ]: 3 : if (args_ptr != nargs_given) {
144 : 0 : g_critical("Excess %zu format args passed for message ID %u",
145 : : nargs_given, unsigned{id});
146 : 0 : return;
147 : : }
148 : :
149 : 3 : message << format_string.substr(copied, std::string::npos);
150 : :
151 : 3 : std::string message_formatted = message.str();
152 : 3 : warn_deprecated_unsafe_internal(cx, id, message_formatted.c_str());
153 [ + - ]: 3 : }
|