1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljsshadowcheck_p.h"
5
6QT_BEGIN_NAMESPACE
7
8using namespace Qt::StringLiterals;
9
10/*!
11 * \internal
12 * \class QQmlJSShadowCheck
13 *
14 * This pass looks for possible shadowing when accessing members of QML-exposed
15 * types. A member can be shadowed if a non-final property is re-declared in a
16 * derived class. As the QML engine will always pick up the most derived variant
17 * of that property, we cannot rely on any property of a type to be actually
18 * accessible, unless one of a few special cases holds:
19 *
20 * 1. We are dealing with a direct scope lookup, without an intermediate object.
21 * Such lookups are protected from shadowing. For example "property int a: b"
22 * always works.
23 * 2. The object we are retrieving the property from is identified by an ID, or
24 * an attached property or a singleton. Such objects cannot be replaced.
25 * Therefore we can be sure to see all the type information at compile time.
26 * 3. The property is declared final.
27 * 4. The object we are retrieving the property from is a value type. Value
28 * types cannot be used polymorphically.
29 *
30 * If the property is potentially shadowed, we can still retrieve it, but we
31 * don't know its type. We should assume "var" then.
32 *
33 * All of the above also holds for methods. There we have to transform the
34 * arguments and return types into "var".
35 */
36
37void QQmlJSShadowCheck::run(
38 InstructionAnnotations *annotations, const Function *function,
39 QQmlJS::DiagnosticMessage *error)
40{
41 m_annotations = annotations;
42 m_function = function;
43 m_error = error;
44 m_state = initialState(function);
45 decode(code: m_function->code.constData(), len: static_cast<uint>(m_function->code.size()));
46}
47
48void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
49{
50 if (!m_state.readsRegister(registerIndex: Accumulator))
51 return; // enum lookup cannot be shadowed.
52
53 auto accumulatorIn = m_state.registers.find(key: Accumulator);
54 if (accumulatorIn != m_state.registers.end()) {
55 checkShadowing(
56 baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->stringForIndex(index: nameIndex),
57 baseRegister: Accumulator);
58 }
59}
60
61void QQmlJSShadowCheck::generate_GetLookup(int index)
62{
63 if (!m_state.readsRegister(registerIndex: Accumulator))
64 return; // enum lookup cannot be shadowed.
65
66 auto accumulatorIn = m_state.registers.find(key: Accumulator);
67 if (accumulatorIn != m_state.registers.end()) {
68 checkShadowing(
69 baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->lookupName(index), baseRegister: Accumulator);
70 }
71}
72
73void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
74{
75 checkShadowing(
76 baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->stringForIndex(index: nameIndex), baseRegister: base);
77}
78
79void QQmlJSShadowCheck::generate_SetLookup(int index, int base)
80{
81 checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index), baseRegister: base);
82}
83
84void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv)
85{
86 Q_UNUSED(argc);
87 Q_UNUSED(argv);
88 checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base);
89}
90
91void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv)
92{
93 Q_UNUSED(argc);
94 Q_UNUSED(argv);
95 checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base);
96}
97
98QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type)
99{
100 m_state = nextStateFromAnnotations(oldState: m_state, annotations: *m_annotations);
101 return (m_state.hasSideEffects() || m_state.changedRegisterIndex() != InvalidRegister)
102 ? ProcessInstruction
103 : SkipInstruction;
104}
105
106void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type)
107{
108}
109
110void QQmlJSShadowCheck::checkShadowing(
111 const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister)
112{
113 if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference)
114 return;
115
116 switch (baseType.variant()) {
117 case QQmlJSRegisterContent::ObjectProperty:
118 case QQmlJSRegisterContent::ExtensionObjectProperty:
119 case QQmlJSRegisterContent::ScopeProperty:
120 case QQmlJSRegisterContent::ExtensionScopeProperty: {
121 const QQmlJSRegisterContent member = m_typeResolver->memberType(type: baseType, name: memberName);
122
123 // You can have something like parent.QtQuick.Screen.pixelDensity
124 // In that case "QtQuick" cannot be resolved as member type and we would later have to look
125 // for "QtQuick.Screen" instead. However, you can only do that with attached properties and
126 // those are not shadowable.
127 if (!member.isValid()) {
128 Q_ASSERT(m_typeResolver->isPrefix(memberName));
129 return;
130 }
131
132 if (member.isProperty()) {
133 if (member.property().isFinal())
134 return; // final properties can't be shadowed
135 } else if (!member.isMethod()) {
136 return; // Only properties and methods can be shadowed
137 }
138
139 m_logger->log(
140 message: u"Member %1 of %2 can be shadowed"_s.arg(
141 args: memberName, args: m_state.accumulatorIn().descriptiveName()),
142 id: qmlCompiler, srcLocation: currentSourceLocation());
143
144 // Make it "var". We don't know what it is.
145 const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
146 const QQmlJSRegisterContent varContent = m_typeResolver->globalType(type: varType);
147 InstructionAnnotation &currentAnnotation = (*m_annotations)[currentInstructionOffset()];
148
149 if (currentAnnotation.changedRegisterIndex != InvalidRegister) {
150 m_typeResolver->adjustOriginalType(
151 tracked: currentAnnotation.changedRegister.storedType(), conversion: varType);
152 m_typeResolver->adjustOriginalType(
153 tracked: m_typeResolver->containedType(container: currentAnnotation.changedRegister), conversion: varType);
154 }
155
156 for (auto it = currentAnnotation.readRegisters.begin(),
157 end = currentAnnotation.readRegisters.end();
158 it != end; ++it) {
159 if (it.key() != baseRegister)
160 it->second.content = m_typeResolver->convert(from: it->second.content, to: varContent);
161 }
162
163 return;
164 }
165 default:
166 // In particular ObjectById is fine as that cannot change into something else
167 // Singleton should also be fine, unless the factory function creates an object
168 // with different property types than the declared class.
169 return;
170 }
171}
172
173QT_END_NAMESPACE
174

source code of qtdeclarative/src/qmlcompiler/qqmljsshadowcheck.cpp